pax_global_header 0000666 0000000 0000000 00000000064 14761776747 0014544 g ustar 00root root 0000000 0000000 52 comment=7785e41995c74ea4825c3fd295b8e1eee46e905b
seadrive-fuse-3.0.13/ 0000775 0000000 0000000 00000000000 14761776747 0014372 5 ustar 00root root 0000000 0000000 seadrive-fuse-3.0.13/.gitignore 0000664 0000000 0000000 00000004435 14761776747 0016370 0 ustar 00root root 0000000 0000000 *~
*.bak
*.o
*.exe
cscope*
*#
Makefile.in
ltmain.sh
libtool
*.lo
*.la
install-sh
depcomp
config.guess
config.h
config.log
config.status
config.sub
config.cache
configure
*/.deps
autom4te*
po/POTFILES
po/Makefile*
po/stamp-it
po/*.gmo
po/*.pot
missing
mkinstalldirs
stamp-h1
*.libs/
Makefile
aclocal.m4
*core
m4/intltool.m4
m4/libtool.m4
m4/ltoptions.m4
m4/ltsugar.m4
m4/ltversion.m4
m4/lt~obsolete.m4
ccnet-*.tar.gz
config.h.in
py-compile
intltool-extract.in
intltool-merge.in
intltool-update.in
*.stamp
*.pyc
*.tmp.ui
*.defs
*.log
.deps
*.db
*.dll
*.aps
*.so
build-stamp
debian/files
debian/seafile
debian/*.substvars
lib/searpc-marshal.h
lib/searpc-signature.h
lib/*.tmp
lib/dir.c
lib/dirent.c
lib/seafile-object.h
lib/task.c
lib/webaccess.c
lib/branch.c
lib/commit.c
lib/crypt.c
lib/repo.c
lib/copy-task.c
app/seafile
app/seafserv-tool
daemon/seaf-daemon
gui/gtk/seafile-applet
server/seaf-server
server/gc/seafserv-gc
monitor/seaf-mon
controller/seafile-controller
fileserver/fileserver
tests/common-conf.sh
tools/seaf-server-init
*.mo
seafile-web
tests/basic/conf1/c882e263e9d02c63ca6b61c68508761cbc74c358.peer
tests/basic/conf1/c882e263e9d02c63ca6b61c68508761cbc74c358.user
tests/basic/conf1/group-db/
tests/basic/conf1/peer-db/
tests/basic/conf1/user-db/
tests/basic/conf2/376cf9b6ef33a6839cf1fc096131893b5ecc673f.peer
tests/basic/conf2/376cf9b6ef33a6839cf1fc096131893b5ecc673f.user
tests/basic/conf2/group-db/
tests/basic/conf2/peer-db/
tests/basic/conf2/user-db/
tests/basic/conf3/1e5b5e0f49010b94aa6c2995a6e7b7cba462d388.peer
tests/basic/conf3/1e5b5e0f49010b94aa6c2995a6e7b7cba462d388.user
tests/basic/conf3/group-db/
tests/basic/conf3/peer-db/
tests/basic/conf3/user-db/
tests/basic/conf4/93ae3e01eea6667cbdd03c4afde413ccd9f1eb43.peer
tests/basic/conf4/93ae3e01eea6667cbdd03c4afde413ccd9f1eb43.user
tests/basic/conf4/peer-db/
tests/basic/conf4/user-db/
web/local_settings.py
gui/win/applet-po-gbk.h
*.dylib
.DS_Store
gui/mac/seafile/build/
gui/mac/seafile/ccnet
gui/mac/seafile/seaf-daemon
gui/mac/seafile/seafileweb.app
web/build/
web/dist/
tests/basic/conf2/misc/
tests/basic/conf2/seafile-data/
tests/test-cdc
tests/test-index
tests/test-seafile-fmt
*.pc
seaf-fsck
*.tar.gz
fuse/seaf-fuse
server/gc/seaf-migrate
/compile
/test-driver
*.dmp
/symbols
src/seadrive
*.wixobj
*.msi
/account.conf
/x64
/.vs
/*.vcxproj.user
seadrive-fuse-3.0.13/LICENSE 0000664 0000000 0000000 00000104515 14761776747 0015405 0 ustar 00root root 0000000 0000000 GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
Copyright (C)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
Copyright (C)
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
.
seadrive-fuse-3.0.13/Makefile.am 0000664 0000000 0000000 00000000551 14761776747 0016427 0 ustar 00root root 0000000 0000000 SUBDIRS = src python
DIST_SUBDIRS = src python
INTLTOOL = \
intltool-extract.in \
intltool-merge.in \
intltool-update.in
# EXTRA_DIST = install-sh $(INTLTOOL) README.markdown scripts debian msi LICENSE.txt
EXTRA_DIST = install-sh $(INTLTOOL) scripts msi debian dmg
ACLOCAL_AMFLAGS = -I m4
dist-hook:
git log --format='%H' -1 > $(distdir)/latest_commit
seadrive-fuse-3.0.13/README.md 0000664 0000000 0000000 00000004055 14761776747 0015655 0 ustar 00root root 0000000 0000000 # seadrive-fuse
SeaDrive daemon with FUSE interface
# Building
## Ubuntu Linux
```
sudo apt-get install autoconf automake libtool libevent-dev libcurl4-openssl-dev libgtk2.0-dev uuid-dev intltool libsqlite3-dev valac libjansson-dev libssl-dev
```
First, you shoud get the latest source of [libsearpc](https://github.com/haiwen/libsearpc) with `v3.2-latest` tag and [seadrive-fuse](https://github.com/haiwen/seadrive-fuse).
To build [seadrive-fuse](https://github.com/haiwen/seadrive-fuse), you need first build [libsearpc](https://github.com/haiwen/libsearpc).
### libsearpc
```
git clone --branch=v3.2-latest https://github.com/haiwen/libsearpc.git
cd libsearpc
./autogen.sh
./configure
make
sudo make install
```
To build [seadrive-fuse](https://github.com/haiwen/seadrive-fuse), you need build [libwebsockets](https://github.com/warmcat/libwebsockets).
### libwebsockets
Also you can build a higher version of libwebsockets by yourself.
Since version 2.0.28, SeaDrive requires libwebsockets version >= 4.0.20. If the version provided by your distribution is lower than the required version, you can choose to disable this function by set --enable-ws to no.
You can also build a higher version of libwebsockets by yourself.
```
git clone --branch=v4.3.0 https://github.com/warmcat/libwebsockets
cd libwebsockets
mkdir build
cd build
cmake ..
make
sudo make install
```
### seadrive-fuse
```
git clone https://github.com/haiwen/seadrive-fuse.git
cd seadrive-fuse
./autogen.sh
./configure
make
sudo make install
```
**Note:** If you plan to package for distribution, you should compile with the latest tag instead of the master branch. Sometimes the latest tag for seadrive-gui project (https://github.com/haiwen/seadrive-gui) is higher than the tag here. In such case you should follow the tag in this project. For example, the latest tag for seadrive-fuse is v2.0.6 while for seadrive-gui is v2.0.7. You should build the package based on v2.0.6 tag from seadrive-gui too. This is because sometimes there is no update related to Linux in the new versions so this project is not updated.
seadrive-fuse-3.0.13/autogen.sh 0000775 0000000 0000000 00000006524 14761776747 0016402 0 ustar 00root root 0000000 0000000 #!/bin/bash
# Run this to generate all the initial makefiles, etc.
: ${AUTOCONF=autoconf}
: ${AUTOHEADER=autoheader}
: ${AUTOMAKE=automake}
: ${ACLOCAL=aclocal}
if test "$(uname)" != "Darwin"; then
: ${LIBTOOLIZE=libtoolize}
else
: ${LIBTOOLIZE=glibtoolize}
fi
: ${INTLTOOLIZE=intltoolize}
: ${LIBTOOL=libtool}
srcdir=`dirname $0`
test -z "$srcdir" && srcdir=.
ORIGDIR=`pwd`
cd $srcdir
PROJECT=ccnet
TEST_TYPE=-f
FILE=net/main.c
CONFIGURE=configure.ac
DIE=0
($AUTOCONF --version) < /dev/null > /dev/null 2>&1 || {
echo
echo "You must have autoconf installed to compile $PROJECT."
echo "Download the appropriate package for your distribution,"
echo "or get the source tarball at ftp://ftp.gnu.org/pub/gnu/"
DIE=1
}
(grep "^AC_PROG_INTLTOOL" $srcdir/$CONFIGURE >/dev/null) && {
($INTLTOOLIZE --version) < /dev/null > /dev/null 2>&1 || {
echo
echo "You must have \`intltoolize' installed to compile $PROJECT."
echo "Get ftp://ftp.gnome.org/pub/GNOME/stable/sources/intltool/intltool-0.22.tar.gz"
echo "(or a newer version if it is available)"
DIE=1
}
}
($AUTOMAKE --version) < /dev/null > /dev/null 2>&1 || {
echo
echo "You must have automake installed to compile $PROJECT."
echo "Get ftp://sourceware.cygnus.com/pub/automake/automake-1.7.tar.gz"
echo "(or a newer version if it is available)"
DIE=1
}
if test "$(uname)" != "Darwin"; then
(grep "^AC_PROG_LIBTOOL" $CONFIGURE >/dev/null) && {
($LIBTOOL --version) < /dev/null > /dev/null 2>&1 || {
echo
echo "**Error**: You must have \`libtool' installed to compile $PROJECT."
echo "Get ftp://ftp.gnu.org/pub/gnu/libtool-1.4.tar.gz"
echo "(or a newer version if it is available)"
DIE=1
}
}
fi
if grep "^AM_[A-Z0-9_]\{1,\}_GETTEXT" "$CONFIGURE" >/dev/null; then
if grep "sed.*POTFILES" "$CONFIGURE" >/dev/null; then
GETTEXTIZE=""
else
if grep "^AM_GLIB_GNU_GETTEXT" "$CONFIGURE" >/dev/null; then
GETTEXTIZE="glib-gettextize"
GETTEXTIZE_URL="ftp://ftp.gtk.org/pub/gtk/v2.0/glib-2.0.0.tar.gz"
else
GETTEXTIZE="gettextize"
GETTEXTIZE_URL="ftp://alpha.gnu.org/gnu/gettext-0.10.35.tar.gz"
fi
$GETTEXTIZE --version < /dev/null > /dev/null 2>&1
if test $? -ne 0; then
echo
echo "**Error**: You must have \`$GETTEXTIZE' installed to compile $PKG_NAME."
echo "Get $GETTEXTIZE_URL"
echo "(or a newer version if it is available)"
DIE=1
fi
fi
fi
if test "$DIE" -eq 1; then
exit 1
fi
dr=`dirname .`
echo processing $dr
aclocalinclude="$aclocalinclude -I m4"
if test x"$MSYSTEM" = x"MINGW32"; then
aclocalinclude="$aclocalinclude -I /mingw32/share/aclocal"
elif test "$(uname)" = "Darwin"; then
aclocalinclude="$aclocalinclude -I /opt/local/share/aclocal"
fi
echo "Creating $dr/aclocal.m4 ..."
test -r $dr/aclocal.m4 || touch $dr/aclocal.m4
echo "Running glib-gettextize... Ignore non-fatal messages."
echo "no" | glib-gettextize --force --copy
echo "Making $dr/aclocal.m4 writable ..."
test -r $dr/aclocal.m4 && chmod u+w $dr/aclocal.m4
echo "Running intltoolize..."
intltoolize --copy --force --automake
echo "Running $LIBTOOLIZE..."
$LIBTOOLIZE --force --copy
echo "Running $ACLOCAL $aclocalinclude ..."
$ACLOCAL $aclocalinclude
echo "Running $AUTOHEADER..."
$AUTOHEADER
echo "Running $AUTOMAKE --gnu $am_opt ..."
$AUTOMAKE --add-missing --gnu $am_opt
echo "Running $AUTOCONF ..."
$AUTOCONF
seadrive-fuse-3.0.13/configure.ac 0000664 0000000 0000000 00000014715 14761776747 0016670 0 ustar 00root root 0000000 0000000 dnl Process this file with autoconf to produce a configure script.
AC_PREREQ(2.61)
AC_INIT([seadrive], [3.0.13], [jonathan.xu@seafile.com])
AC_CONFIG_HEADER([config.h])
AC_CONFIG_MACRO_DIR([m4])
AM_INIT_AUTOMAKE([1.9 foreign])
AC_CANONICAL_BUILD
dnl enable the build of share library by default
AC_ENABLE_SHARED
AC_SUBST(LIBTOOL_DEPS)
# Checks for programs.
AC_PROG_CC
#AM_C_PROTOTYPES
AC_C_CONST
AC_PROG_MAKE_SET
# AC_PROG_RANLIB
LT_INIT
# Checks for headers.
#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])
# Checks for typedefs, structures, and compiler characteristics.
AC_SYS_LARGEFILE
# Checks for library functions.
#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])
# check platform
AC_MSG_CHECKING(for WIN32)
if test "$build_os" = "mingw32" -o "$build_os" = "mingw64"; then
bwin32=true
AC_MSG_RESULT(compile in mingw)
else
AC_MSG_RESULT(no)
fi
AC_MSG_CHECKING(for Mac)
if test "$(uname)" = "Darwin"; then
bmac=true
AC_MSG_RESULT(compile in mac)
else
AC_MSG_RESULT(no)
fi
AC_MSG_CHECKING(for Linux)
if test "$bmac" != "true" -a "$bwin32" != "true"; then
blinux=true
AC_MSG_RESULT(compile in linux)
else
AC_MSG_RESULT(no)
fi
AM_CONDITIONAL([WIN32], [test "$bwin32" = "true"])
AM_CONDITIONAL([MACOS], [test "$bmac" = "true"])
AM_CONDITIONAL([LINUX], [test "$blinux" = "true"])
# check libraries
if test "$bwin32" != true; then
if test "$bmac" = true; then
AC_CHECK_LIB(c, uuid_generate, [echo "found library uuid"],
AC_MSG_ERROR([*** Unable to find uuid_generate in libc]), )
else
AC_CHECK_LIB(uuid, uuid_generate, [echo "found library uuid"],
AC_MSG_ERROR([*** Unable to find uuid library]), )
fi
fi
AC_CHECK_LIB(pthread, pthread_create, [echo "found library pthread"], AC_MSG_ERROR([*** Unable to find pthread library]), )
AC_CHECK_LIB(sqlite3, sqlite3_open,[echo "found library sqlite3"] , AC_MSG_ERROR([*** Unable to find sqlite3 library]), )
dnl Do we need to use AX_LIB_SQLITE3 to check sqlite?
dnl AX_LIB_SQLITE3
CONSOLE=
if test "$bwin32" = "true"; then
AC_ARG_ENABLE(console, AC_HELP_STRING([--enable-console], [enable console]),
[console=$enableval],[console="yes"])
if test x${console} != xyes ; then
CONSOLE="-Wl,--subsystem,windows -Wl,--entry,_mainCRTStartup"
fi
fi
AC_SUBST(CONSOLE)
if test "$bwin32" = true; then
LIB_WS32=-lws2_32
LIB_GDI32=-lgdi32
LIB_RT=
LIB_INTL=-lintl
LIBS=
LIB_RESOLV=
LIB_UUID=-lRpcrt4
LIB_IPHLPAPI=-liphlpapi
LIB_SHELL32=-lshell32
LIB_PSAPI=-lpsapi
LIB_MAC=
MSVC_CFLAGS="-D__MSVCRT__ -D__MSVCRT_VERSION__=0x0601"
LIB_CRYPT32=-lcrypt32
elif test "$bmac" = true ; then
LIB_WS32=
LIB_GDI32=
LIB_RT=
LIB_INTL=
LIB_RESOLV=-lresolv
LIB_UUID=
LIB_IPHLPAPI=
LIB_SHELL32=
LIB_PSAPI=
MSVC_CFLAGS=
LIB_MAC="-framework CoreServices"
LIB_CRYPT32=
LIB_ICONV=-liconv
else
LIB_WS32=
LIB_GDI32=
LIB_RT=
LIB_INTL=
LIB_RESOLV=-lresolv
LIB_UUID=-luuid
LIB_IPHLPAPI=
LIB_SHELL32=
LIB_PSAPI=
LIB_MAC=
MSVC_CFLAGS=
LIB_CRYPT32=
fi
AC_SUBST(LIB_WS32)
AC_SUBST(LIB_GDI32)
AC_SUBST(LIB_RT)
AC_SUBST(LIB_INTL)
AC_SUBST(LIB_RESOLV)
AC_SUBST(LIB_UUID)
AC_SUBST(LIB_IPHLPAPI)
AC_SUBST(LIB_SHELL32)
AC_SUBST(LIB_PSAPI)
AC_SUBST(LIB_MAC)
AC_SUBST(MSVC_CFLAGS)
AC_SUBST(LIB_CRYPT32)
AC_SUBST(LIB_ICONV)
LIBEVENT_REQUIRED=2.0
GLIB_REQUIRED=2.16.0
SEARPC_REQUIRED=1.0
JANSSON_REQUIRED=2.2.1
CURL_REQUIRED=7.17
FUSE_REQUIRED=2.7.3
ZLIB_REQUIRED=1.2.0
WS_REQUIRED=4.0.20
GNUTLS_REQUIRED=3.3.0
AC_ARG_WITH([gpl-crypto],
AS_HELP_STRING([--with-gpl-crypto=[yes|no]],
[Use GPL compatible crypto libraries. Default no.]),
[ gpl_crypto=$with_gpl_crypto ],
[ gpl_crypto="no"])
AS_IF([test "x$gpl_crypto" = "xyes"], [
PKG_CHECK_MODULES([GNUTLS], [gnutls >= $GNUTLS_REQUIRED])
AC_SUBST([GNUTLS_CFLAGS])
AC_SUBST([GNUTLS_LIBS])
PKG_CHECK_MODULES([NETTLE], [nettle])
AC_SUBST([NETTLE_CFLAGS])
AC_SUBST([NETTLE_LIBS])
AC_DEFINE([USE_GPL_CRYPTO], [1], [Use GPL-compatible crypto libraries])
], [
AC_CHECK_LIB(crypto, SHA1_Init, [echo "found library crypto"], AC_MSG_ERROR([*** Unable to find openssl crypto library]), )
PKG_CHECK_MODULES([SSL], [openssl])
AC_SUBST([SSL_CFLAGS])
AC_SUBST([SSL_LIBS])
])
PKG_CHECK_MODULES(GLIB2, [glib-2.0 >= $GLIB_REQUIRED])
AC_SUBST(GLIB2_CFLAGS)
AC_SUBST(GLIB2_LIBS)
PKG_CHECK_MODULES(GOBJECT, [gobject-2.0 >= $GLIB_REQUIRED])
AC_SUBST(GOBJECT_CFLAGS)
AC_SUBST(GOBJECT_LIBS)
PKG_CHECK_MODULES(SEARPC, [libsearpc >= $SEARPC_REQUIRED])
AC_SUBST(SEARPC_CFLAGS)
AC_SUBST(SEARPC_LIBS)
PKG_CHECK_MODULES(JANSSON, [jansson >= $JANSSON_REQUIRED])
AC_SUBST(JANSSON_CFLAGS)
AC_SUBST(JANSSON_LIBS)
PKG_CHECK_MODULES(LIBEVENT, [libevent >= $LIBEVENT_REQUIRED])
AC_SUBST(LIBEVENT_CFLAGS)
AC_SUBST(LIBEVENT_LIBS)
PKG_CHECK_MODULES(ZLIB, [zlib >= $ZLIB_REQUIRED])
AC_SUBST(ZLIB_CFLAGS)
AC_SUBST(ZLIB_LIBS)
if test "${blinux}" = "true" -o "$bmac" = "true"; then
PKG_CHECK_MODULES(FUSE, [fuse >= $FUSE_REQUIRED])
AC_SUBST(FUSE_CFLAGS)
AC_SUBST(FUSE_LIBS)
fi
PKG_CHECK_MODULES(CURL, [libcurl >= $CURL_REQUIRED])
AC_SUBST(CURL_CFLAGS)
AC_SUBST(CURL_LIBS)
PKG_CHECK_MODULES(ARGON2, [libargon2])
AC_SUBST(ARGON2_CFLAGS)
AC_SUBST(ARGON2_LIBS)
AC_ARG_ENABLE(ws, AC_HELP_STRING([--enable-ws], [enable build websockets]),
[compile_ws=$enableval],[compile_ws="yes"])
AM_CONDITIONAL([COMPILE_WS], [test "${compile_ws}" = "yes"])
if test "${compile_ws}" = "yes"; then
PKG_CHECK_MODULES(WS, [libwebsockets >= $WS_REQUIRED])
AC_DEFINE(COMPILE_WS, 1, [compile linux websockets])
AC_SUBST(WS_CFLAGS)
AC_SUBST(WS_LIBS)
fi
BPWRAPPER_REQUIRED=0.1
AC_ARG_ENABLE(breakpad, AC_HELP_STRING([--enable-breakpad], [build google breadpad support]),
[compile_breakpad=$enableval],[compile_breakpad="no"])
AM_CONDITIONAL([HAVE_BREAKPAD_SUPPORT], [test "${compile_breakpad}" = "yes"])
if test "${compile_breakpad}" = "yes"; then
PKG_CHECK_MODULES(BPWRAPPER, [bpwrapper])
AC_DEFINE(HAVE_BREAKPAD_SUPPORT, 1, [Breakpad support enabled])
AC_SUBST(BPWRAPPER_CFLAGS)
AC_SUBST(BPWRAPPER_LIBS)
fi
AM_PATH_PYTHON([2.6])
ac_configure_args="$ac_configure_args -q"
AC_CONFIG_FILES(
Makefile
src/Makefile
python/Makefile
python/seadrive/Makefile
)
AC_OUTPUT
seadrive-fuse-3.0.13/debian/ 0000775 0000000 0000000 00000000000 14761776747 0015614 5 ustar 00root root 0000000 0000000 seadrive-fuse-3.0.13/debian/README.Debian 0000664 0000000 0000000 00000000273 14761776747 0017657 0 ustar 00root root 0000000 0000000 Seafile Drive Client
-------
For more information about Seafile Drive Client, please visit http://seafile.com
-- Jonathan Xu Fri, 24 Feb 2017 15:27:16 +0800
seadrive-fuse-3.0.13/debian/changelog 0000664 0000000 0000000 00000015262 14761776747 0017474 0 ustar 00root root 0000000 0000000 seadrive-daemon (3.0.13) unstable; urgency=low
* new upstream release
-- Jonathan Xu Web, 5 Mar 2025 10:30:00 +0800
seadrive-daemon (3.0.12) unstable; urgency=low
* new upstream release
-- Jonathan Xu Tue, 9 Jan 2025 10:30:00 +0800
seadrive-daemon (2.0.28) unstable; urgency=low
* new upstream release
-- Jonathan Xu Tue, 15 Aug 2023 13:56:30 +0800
seadrive-daemon (2.0.27) unstable; urgency=low
* new upstream release
-- Jonathan Xu Mon, 17 July 2023 14:56:30 +0800
seadrive-daemon (2.0.22) unstable; urgency=low
* new upstream release
-- Jonathan Xu Tue, 21 June 2022 16:56:30 +0800
seadrive-daemon (2.0.16) unstable; urgency=low
* new upstream release
-- Jonathan Xu Tue, 7 Sep 2021 16:56:30 +0800
seadrive-daemon (2.0.15) unstable; urgency=low
* new upstream release
-- Jonathan Xu Fri, 20 Aug 2021 15:56:30 +0800
seadrive-daemon (2.0.14) unstable; urgency=low
* new upstream release
-- Jonathan Xu Thu, 20 May 2021 15:36:28 +0800
seadrive-daemon (2.0.13) unstable; urgency=low
* new upstream release
-- Jonathan Xu Thu, 18 Mar 2021 16:18:21 +0800
seadrive-daemon (2.0.12) unstable; urgency=low
* new upstream release
-- Jonathan Xu Mon, 25 Jan 2021 17:22:17 +0800
seadrive-daemon (2.0.10) unstable; urgency=low
* new upstream release
-- Jonathan Xu Thu, 24 Dec 2020 16:48:30 +0800
seadrive-daemon (2.0.6) unstable; urgency=low
* new upstream release
-- Jonathan Xu Wed, 9 Sep 2020 14:38:39 +0800
seadrive-daemon (2.0.5) unstable; urgency=low
* new upstream release
-- Jonathan Xu Wed, 29 Jul 2020 09:53:43 +0800
seadrive-daemon (2.0.4) unstable; urgency=low
* new upstream release
-- Jonathan Xu Sat, 9 Jul 2020 18:07:19 +0800
seadrive-daemon (2.0.3) unstable; urgency=low
* new upstream release
-- Jonathan Xu Tue, 16 Jun 2020 10:03:00 +0800
seadrive-daemon (2.0.2) unstable; urgency=low
* new upstream release
-- Jonathan Xu Mon, 18 May 2020 11:57:00 +0800
seadrive-daemon (1.0.11) unstable; urgency=low
* new upstream release
-- Jonathan Xu Sat, 11 Jan 2020 11:57:00 +0800
seadrive-daemon (1.0.10) unstable; urgency=low
* new upstream release
-- Jonathan Xu Thu, 19 Dec 2019 14:36:00 +0800
seadrive-daemon (1.0.9) unstable; urgency=low
* new upstream release
-- Jonathan Xu Sat, 30 Nov 2019 14:36:00 +0800
seadrive-daemon (1.0.8) unstable; urgency=low
* new upstream release
-- Jonathan Xu Thu, 24 Oct 2019 15:02:11 +0800
seadrive-daemon (1.0.7) unstable; urgency=low
* new upstream release
-- Jonathan Xu Thu, 25 Jul 2019 10:01:10 +0800
seadrive-daemon (1.0.6) unstable; urgency=low
* new upstream release
-- Jonathan Xu Mon, 24 Jun 2019 14:20:20 +0800
seadrive-daemon (1.0.5) unstable; urgency=low
* new upstream release
-- Jonathan Xu Tue, 4 Jun 2019 15:54:20 +0800
seadrive-daemon (1.0.4) unstable; urgency=low
* new upstream release
-- Jonathan Xu Fri, 19 Apr 2019 16:28:28 +0800
seadrive-daemon (1.0.3) unstable; urgency=low
* new upstream release
-- Jonathan Xu Tue, 15 Mar 2019 11:02:28 +0800
seadrive-daemon (1.0.2) unstable; urgency=low
* new upstream release
-- Jonathan Xu Tue, 5 Mar 2019 11:02:28 +0800
seadrive-daemon (1.0.1) unstable; urgency=low
* new upstream release
-- Jonathan Xu Fri, 11 Jan 2019 15:58:16 +0800
seadrive-daemon (1.0.0) unstable; urgency=low
* new upstream release
-- Jonathan Xu Wed, 29 Aug 2018 15:37:16 +0800
seadrive-daemon (0.9.7) unstable; urgency=low
* new upstream release
-- Jonathan Xu Thu, 25 Oct 2018 18:03:16 +0800
seadrive-daemon (0.9.6) unstable; urgency=low
* new upstream release
-- Jonathan Xu Fri, 28 Sep 2018 14:57:16 +0800
seadrive-daemon (0.9.5) unstable; urgency=low
* new upstream release
-- Jonathan Xu Wed, 29 Aug 2018 15:37:16 +0800
seadrive-daemon (0.9.4) unstable; urgency=low
* new upstream release
-- Jonathan Xu Fri, 17 Aug 2018 11:18:16 +0800
seadrive-daemon (0.9.3) unstable; urgency=low
* new upstream release
-- Jonathan Xu Tue, 12 May 2018 14:38:16 +0800
seadrive-daemon (0.9.2) unstable; urgency=low
* new upstream release
-- Jonathan Xu Fri, 4 May 2018 17:33:16 +0800
seadrive-daemon (0.9.1) unstable; urgency=low
* new upstream release
-- Jonathan Xu Wed, 25 Apr 2018 15:21:16 +0800
seadrive-daemon (0.9.0) unstable; urgency=low
* new upstream release
-- Jonathan Xu Fri, 20 Apr 2018 15:21:16 +0800
seadrive-daemon (0.8.6) unstable; urgency=low
* new upstream release
-- Jonathan Xu Thur, 8 Mar 2018 18:30:16 +0800
seadrive-daemon (0.8.5) unstable; urgency=low
* new upstream release
-- Jonathan Xu Tue, 2 Jan 2018 14:53:16 +0800
seadrive-daemon (0.8.4) unstable; urgency=low
* new upstream release
-- Jonathan Xu Thur, 30 Nov 2017 14:05:16 +0800
seadrive-daemon (0.8.3) unstable; urgency=low
* new upstream release
-- Jonathan Xu Fri, 24 Nov 2017 14:11:16 +0800
seadrive-daemon (0.8.2) unstable; urgency=low
* new upstream release
-- Jonathan Xu Thur, 9 Nov 2017 17:54:16 +0800
seadrive-daemon (0.8.1) unstable; urgency=low
* new upstream release
-- Jonathan Xu Fri, 3 Nov 2017 15:16:16 +0800
seadrive-daemon (0.8.0) unstable; urgency=low
* new upstream release
-- Jonathan Xu Sat, 9 Sep 2017 18:03:16 +0800
seadrive-daemon (0.7.0) unstable; urgency=low
* new upstream release
-- Jonathan Xu Tue, 6 Jun 2017 18:03:16 +0800
seadrive-daemon (0.6.1) unstable; urgency=low
* new upstream release
-- Jonathan Xu Mon, 27 Mar 2017 15:27:16 +0800
seadrive-daemon (0.5.1) unstable; urgency=low
* new upstream release
-- Jonathan Xu Fri, 24 Feb 2017 15:27:16 +0800
seadrive-fuse-3.0.13/debian/compat 0000664 0000000 0000000 00000000002 14761776747 0017012 0 ustar 00root root 0000000 0000000 7
seadrive-fuse-3.0.13/debian/control 0000664 0000000 0000000 00000002072 14761776747 0017220 0 ustar 00root root 0000000 0000000 Source: seadrive-daemon
Section: net
Priority: extra
Maintainer: Jonathan Xu
Build-Depends:
debhelper (>= 7),
autotools-dev,
libssl-dev,
libsqlite3-dev,
intltool,
libglib2.0-dev,
libevent-dev,
uuid-dev,
libtool,
libcurl4-openssl-dev,
valac,
libjansson-dev,
dh-python,
libfuse-dev,
libsearpc-dev,
Standards-Version: 3.9.5
Homepage: http://seafile.com
Package: seadrive-daemon
Section: net
Architecture: any
Depends:
${shlibs:Depends},
${misc:Depends},
${python:Depends},
Suggests: seadrive-gui, seadrive-cli
Description: SeaDrive daemon
File syncing and sharing software with file encryption and group
sharing, emphasis on reliability and high performance.
.
This package contains the SeaDrive daemon.
Package: seadrive-daemon-dbg
Section: debug
Architecture: any
Depends:
seadrive-daemon (= ${binary:Version}),
${misc:Depends},
Description: Debugging symbols for the seadrive-daemon package.
This package contains the debugging symbols for the seadrive-daemon package.
seadrive-fuse-3.0.13/debian/copyright 0000664 0000000 0000000 00000105030 14761776747 0017546 0 ustar 00root root 0000000 0000000 Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: seadrive
Upstream-Contact: Jonathan Xu
Source: https://github.com/haiwen/seadrive-fuse
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
Copyright (C)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
Copyright (C)
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
.
seadrive-fuse-3.0.13/debian/dirs 0000664 0000000 0000000 00000000007 14761776747 0016475 0 ustar 00root root 0000000 0000000 usr/bin seadrive-fuse-3.0.13/debian/docs 0000664 0000000 0000000 00000000000 14761776747 0016455 0 ustar 00root root 0000000 0000000 seadrive-fuse-3.0.13/debian/patches/ 0000775 0000000 0000000 00000000000 14761776747 0017243 5 ustar 00root root 0000000 0000000 seadrive-fuse-3.0.13/debian/patches/series 0000664 0000000 0000000 00000000000 14761776747 0020446 0 ustar 00root root 0000000 0000000 seadrive-fuse-3.0.13/debian/rules 0000775 0000000 0000000 00000000542 14761776747 0016675 0 ustar 00root root 0000000 0000000 #!/usr/bin/make -f
# -*- makefile -*-
%:
dh $@ --with python3 --with autotools_dev
override_dh_auto_configure:
./autogen.sh
dh_auto_configure -- --disable-fuse
override_dh_auto_test:
# make check seems to be broken
override_dh_strip:
dh_strip -pseadrive-daemon --dbg-package=seadrive-daemon-dbg
override_dh_auto_build:
dh_auto_build --parallel
seadrive-fuse-3.0.13/debian/seadrive-daemon.install 0000664 0000000 0000000 00000000021 14761776747 0022240 0 ustar 00root root 0000000 0000000 usr/bin/seadrive
seadrive-fuse-3.0.13/debian/source/ 0000775 0000000 0000000 00000000000 14761776747 0017114 5 ustar 00root root 0000000 0000000 seadrive-fuse-3.0.13/debian/source/format 0000664 0000000 0000000 00000000014 14761776747 0020322 0 ustar 00root root 0000000 0000000 3.0 (quilt)
seadrive-fuse-3.0.13/python/ 0000775 0000000 0000000 00000000000 14761776747 0015713 5 ustar 00root root 0000000 0000000 seadrive-fuse-3.0.13/python/Makefile.am 0000664 0000000 0000000 00000000023 14761776747 0017742 0 ustar 00root root 0000000 0000000 SUBDIRS = seadrive
seadrive-fuse-3.0.13/python/seadrive/ 0000775 0000000 0000000 00000000000 14761776747 0017515 5 ustar 00root root 0000000 0000000 seadrive-fuse-3.0.13/python/seadrive/Makefile.am 0000664 0000000 0000000 00000000116 14761776747 0021547 0 ustar 00root root 0000000 0000000 seadrivedir=${pyexecdir}/seadrive
seadrive_PYTHON = __init__.py rpcclient.py
seadrive-fuse-3.0.13/python/seadrive/__init__.py 0000664 0000000 0000000 00000000066 14761776747 0021630 0 ustar 00root root 0000000 0000000
from rpcclient import SeadriveRpcClient as RpcClient
seadrive-fuse-3.0.13/python/seadrive/rpcclient.py 0000664 0000000 0000000 00000001370 14761776747 0022053 0 ustar 00root root 0000000 0000000 from pysearpc import searpc_func, SearpcError, NamedPipeClient
class SeadriveRpcClient(NamedPipeClient):
"""RPC used in client"""
def __init__(self, socket_path, *args, **kwargs):
NamedPipeClient.__init__(
self,
socket_path,
"seadrive-rpcserver",
*args,
**kwargs
)
@searpc_func("int", [])
def seafile_enable_auto_sync():
pass
enable_auto_sync = seafile_enable_auto_sync
@searpc_func("int", [])
def seafile_disable_auto_sync():
pass
disable_auto_sync = seafile_disable_auto_sync
@searpc_func("json", [])
def seafile_get_global_sync_status():
pass
get_global_sync_status = seafile_get_global_sync_status
seadrive-fuse-3.0.13/src/ 0000775 0000000 0000000 00000000000 14761776747 0015161 5 ustar 00root root 0000000 0000000 seadrive-fuse-3.0.13/src/Makefile.am 0000664 0000000 0000000 00000003473 14761776747 0017224 0 ustar 00root root 0000000 0000000
AM_CFLAGS = -DPKGDATADIR=\"$(pkgdatadir)\" \
-DPACKAGE_DATA_DIR=\""$(pkgdatadir)"\" \
@GLIB2_CFLAGS@ \
@MSVC_CFLAGS@ \
@CURL_CFLAGS@ \
@BPWRAPPER_CFLAGS@ \
@FUSE_CFLAGS@ \
@SEARPC_CFLAGS@ \
@GNUTLS_CFLAGS@ \
-Wall
if WIN32
AM_CFLAGS += -I/usr/local/include
endif
bin_PROGRAMS = seadrive
noinst_HEADERS = \
http-tx-mgr.h \
sync-mgr.h \
seafile-session.h \
rpc-service.h \
fuse-ops.h \
repo-tree.h \
journal-mgr.h \
change-set.h \
fs-mgr.h \
repo-mgr.h \
commit-mgr.h \
branch-mgr.h \
filelock-mgr.h \
obj-store.h \
block-mgr.h \
block.h \
block-backend.h \
seafile-crypt.h \
password-hash.h \
diff-simple.h \
seafile-config.h \
seafile-error.h \
log.h \
curl-init.h \
cdc.h \
rabin-checksum.h \
db.h \
job-mgr.h \
timer.h \
utils.h \
file-cache-mgr.h \
sync-status-tree.h \
mq-mgr.h \
notif-mgr.h \
common.h \
obj-backend.h \
searpc-signature.h \
searpc-marshal.h
if COMPILE_WS
ws_src = notif-mgr.c
endif
seadrive_SOURCES = \
seadrive.c \
http-tx-mgr.c \
sync-mgr.c \
seafile-session.c \
rpc-service.c \
fuse-ops.c \
repo-tree.c \
journal-mgr.c \
change-set.c \
fs-mgr.c \
repo-mgr.c \
commit-mgr.c \
branch-mgr.c \
filelock-mgr.c \
obj-store.c \
obj-backend-fs.c \
block-mgr.c \
block-backend.c \
block-backend-fs.c \
seafile-crypt.c \
password-hash.c \
diff-simple.c \
seafile-config.c \
log.c \
curl-init.c \
cdc.c \
rabin-checksum.c \
db.c \
job-mgr.c \
timer.c \
utils.c \
file-cache-mgr.c \
sync-status-tree.c \
mq-mgr.c \
$(ws_src)
seadrive_LDADD = @LIB_INTL@ @GLIB2_LIBS@ @GOBJECT_LIBS@ \
@SSL_LIBS@ @GNUTLS_LIBS@ @NETTLE_LIBS@ @LIB_RT@ @LIB_UUID@ -lsqlite3 @LIBEVENT_LIBS@ \
@LIB_WS32@ @LIB_CRYPT32@ \
@JANSSON_LIBS@ @LIB_MAC@ @ZLIB_LIBS@ @CURL_LIBS@ @BPWRAPPER_LIBS@ \
@FUSE_LIBS@ @SEARPC_LIBS@ @WS_LIBS@ @ARGON2_LIBS@
seadrive_LDFLAGS = @CONSOLE@
seadrive-fuse-3.0.13/src/block-backend-fs.c 0000664 0000000 0000000 00000030025 14761776747 0020412 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#include "common.h"
#include "utils.h"
#include "log.h"
#include
#include
#include "block-backend.h"
struct _BHandle {
char *store_id;
int version;
char block_id[41];
int fd;
int rw_type;
char *tmp_file;
};
typedef struct {
char *v0_block_dir;
int v0_block_dir_len;
char *block_dir;
int block_dir_len;
char *tmp_dir;
int tmp_dir_len;
} FsPriv;
static char *
get_block_path (BlockBackend *bend,
const char *block_sha1,
char path[],
const char *store_id,
int version);
static int
open_tmp_file (BlockBackend *bend,
const char *basename,
char **path);
static BHandle *
block_backend_fs_open_block (BlockBackend *bend,
const char *store_id,
int version,
const char *block_id,
int rw_type)
{
BHandle *handle;
int fd = -1;
char *tmp_file;
g_return_val_if_fail (block_id != NULL, NULL);
g_return_val_if_fail (strlen(block_id) == 40, NULL);
g_return_val_if_fail (rw_type == BLOCK_READ || rw_type == BLOCK_WRITE, NULL);
if (rw_type == BLOCK_READ) {
char path[SEAF_PATH_MAX];
get_block_path (bend, block_id, path, store_id, version);
fd = g_open (path, O_RDONLY | O_BINARY, 0);
if (fd < 0) {
seaf_warning ("[block bend] failed to open block %s for read: %s\n",
block_id, strerror(errno));
return NULL;
}
} else {
fd = open_tmp_file (bend, block_id, &tmp_file);
if (fd < 0) {
seaf_warning ("[block bend] failed to open block %s for write: %s\n",
block_id, strerror(errno));
return NULL;
}
}
handle = g_new0(BHandle, 1);
handle->fd = fd;
memcpy (handle->block_id, block_id, 41);
handle->rw_type = rw_type;
if (rw_type == BLOCK_WRITE)
handle->tmp_file = tmp_file;
if (store_id)
handle->store_id = g_strdup(store_id);
handle->version = version;
return handle;
}
static int
block_backend_fs_read_block (BlockBackend *bend,
BHandle *handle,
void *buf, int len)
{
return (readn (handle->fd, buf, len));
}
static int
block_backend_fs_write_block (BlockBackend *bend,
BHandle *handle,
const void *buf, int len)
{
return (writen (handle->fd, buf, len));
}
static int
block_backend_fs_close_block (BlockBackend *bend,
BHandle *handle)
{
int ret;
ret = close (handle->fd);
return ret;
}
static void
block_backend_fs_block_handle_free (BlockBackend *bend,
BHandle *handle)
{
if (handle->rw_type == BLOCK_WRITE) {
/* make sure the tmp file is removed even on failure. */
g_unlink (handle->tmp_file);
g_free (handle->tmp_file);
}
g_free (handle->store_id);
g_free (handle);
}
static int
create_parent_path (const char *path)
{
char *dir = g_path_get_dirname (path);
if (!dir)
return -1;
if (g_file_test (dir, G_FILE_TEST_EXISTS)) {
g_free (dir);
return 0;
}
if (g_mkdir_with_parents (dir, 0777) < 0) {
seaf_warning ("Failed to create object parent path: %s.\n", dir);
g_free (dir);
return -1;
}
g_free (dir);
return 0;
}
static int
block_backend_fs_commit_block (BlockBackend *bend,
BHandle *handle)
{
char path[SEAF_PATH_MAX];
g_return_val_if_fail (handle->rw_type == BLOCK_WRITE, -1);
get_block_path (bend, handle->block_id, path, handle->store_id, handle->version);
if (create_parent_path (path) < 0) {
seaf_warning ("Failed to create path for block %s:%s.\n",
handle->store_id, handle->block_id);
return -1;
}
if (g_file_test(path, G_FILE_TEST_EXISTS)) {
if (g_unlink (path) < 0) {
seaf_warning ("[block bend] failed to remove existing block %s: %s\n",
path, strerror(errno));
}
}
if (g_rename (handle->tmp_file, path) < 0) {
seaf_warning ("[block bend] failed to commit block %s:%s: %s\n",
handle->store_id, handle->block_id, strerror(errno));
return -1;
}
return 0;
}
static gboolean
block_backend_fs_block_exists (BlockBackend *bend,
const char *store_id,
int version,
const char *block_sha1)
{
char block_path[SEAF_PATH_MAX];
get_block_path (bend, block_sha1, block_path, store_id, version);
return g_file_test(block_path, G_FILE_TEST_EXISTS);
}
static int
block_backend_fs_remove_block (BlockBackend *bend,
const char *store_id,
int version,
const char *block_id)
{
char path[SEAF_PATH_MAX];
get_block_path (bend, block_id, path, store_id, version);
return g_unlink (path);
}
static BMetadata *
block_backend_fs_stat_block (BlockBackend *bend,
const char *store_id,
int version,
const char *block_id)
{
char path[SEAF_PATH_MAX];
SeafStat st;
BMetadata *block_md;
get_block_path (bend, block_id, path, store_id, version);
if (seaf_stat (path, &st) < 0) {
seaf_warning ("[block bend] Failed to stat block %s:%s at %s: %s.\n",
store_id, block_id, path, strerror(errno));
return NULL;
}
block_md = g_new0(BMetadata, 1);
memcpy (block_md->id, block_id, 40);
block_md->size = (uint32_t) st.st_size;
return block_md;
}
static BMetadata *
block_backend_fs_stat_block_by_handle (BlockBackend *bend,
BHandle *handle)
{
SeafStat st;
BMetadata *block_md;
if (seaf_fstat (handle->fd, &st) < 0) {
seaf_warning ("[block bend] Failed to stat block %s:%s.\n",
handle->store_id, handle->block_id);
return NULL;
}
block_md = g_new0(BMetadata, 1);
memcpy (block_md->id, handle->block_id, 40);
block_md->size = (uint32_t) st.st_size;
return block_md;
}
static int
block_backend_fs_foreach_block (BlockBackend *bend,
const char *store_id,
int version,
SeafBlockFunc process,
void *user_data)
{
FsPriv *priv = bend->be_priv;
char *block_dir = NULL;
int dir_len;
GDir *dir1 = NULL, *dir2;
const char *dname1, *dname2;
char block_id[128];
char path[SEAF_PATH_MAX], *pos;
int ret = 0;
#if defined MIGRATION
if (version > 0)
block_dir = g_build_filename (priv->block_dir, store_id, NULL);
else
block_dir = g_strdup(priv->v0_block_dir);
#else
block_dir = g_build_filename (priv->block_dir, store_id, NULL);
#endif
dir_len = strlen (block_dir);
dir1 = g_dir_open (block_dir, 0, NULL);
if (!dir1) {
goto out;
}
memcpy (path, block_dir, dir_len);
pos = path + dir_len;
while ((dname1 = g_dir_read_name(dir1)) != NULL) {
snprintf (pos, sizeof(path) - dir_len, "/%s", dname1);
dir2 = g_dir_open (path, 0, NULL);
if (!dir2) {
seaf_warning ("Failed to open block dir %s.\n", path);
continue;
}
while ((dname2 = g_dir_read_name(dir2)) != NULL) {
snprintf (block_id, sizeof(block_id), "%s%s", dname1, dname2);
if (!process (store_id, version, block_id, user_data)) {
g_dir_close (dir2);
goto out;
}
}
g_dir_close (dir2);
}
out:
if (dir1)
g_dir_close (dir1);
g_free (block_dir);
return ret;
}
static int
block_backend_fs_remove_store (BlockBackend *bend, const char *store_id)
{
FsPriv *priv = bend->be_priv;
char *block_dir = NULL;
GDir *dir1, *dir2;
const char *dname1, *dname2;
char *path1, *path2;
block_dir = g_build_filename (priv->block_dir, store_id, NULL);
dir1 = g_dir_open (block_dir, 0, NULL);
if (!dir1) {
g_free (block_dir);
return 0;
}
while ((dname1 = g_dir_read_name(dir1)) != NULL) {
path1 = g_build_filename (block_dir, dname1, NULL);
dir2 = g_dir_open (path1, 0, NULL);
if (!dir2) {
seaf_warning ("Failed to open block dir %s.\n", path1);
g_dir_close (dir1);
g_free (path1);
g_free (block_dir);
return -1;
}
while ((dname2 = g_dir_read_name(dir2)) != NULL) {
path2 = g_build_filename (path1, dname2, NULL);
g_unlink (path2);
g_free (path2);
}
g_dir_close (dir2);
g_rmdir (path1);
g_free (path1);
}
g_dir_close (dir1);
g_rmdir (block_dir);
g_free (block_dir);
return 0;
}
static char *
get_block_path (BlockBackend *bend,
const char *block_sha1,
char path[],
const char *store_id,
int version)
{
FsPriv *priv = bend->be_priv;
char *pos = path;
int n;
#if defined MIGRATION
if (version > 0) {
n = snprintf (path, SEAF_PATH_MAX, "%s/%s/", priv->block_dir, store_id);
pos += n;
} else {
memcpy (pos, priv->v0_block_dir, priv->v0_block_dir_len);
pos[priv->v0_block_dir_len] = '/';
pos += priv->v0_block_dir_len + 1;
}
#else
n = snprintf (path, SEAF_PATH_MAX, "%s/%s/", priv->block_dir, store_id);
pos += n;
#endif
memcpy (pos, block_sha1, 2);
pos[2] = '/';
pos += 3;
memcpy (pos, block_sha1 + 2, 41 - 2);
return path;
}
static int
open_tmp_file (BlockBackend *bend,
const char *basename,
char **path)
{
FsPriv *priv = bend->be_priv;
int fd;
*path = g_strdup_printf ("%s/%s.XXXXXX", priv->tmp_dir, basename);
fd = g_mkstemp (*path);
if (fd < 0)
g_free (*path);
return fd;
}
BlockBackend *
block_backend_fs_new (const char *seaf_dir, const char *tmp_dir)
{
BlockBackend *bend;
FsPriv *priv;
bend = g_new0(BlockBackend, 1);
priv = g_new0(FsPriv, 1);
bend->be_priv = priv;
priv->v0_block_dir = g_build_filename (seaf_dir, "blocks", NULL);
priv->v0_block_dir_len = strlen(priv->v0_block_dir);
priv->block_dir = g_build_filename (seaf_dir, "storage", "blocks", NULL);
priv->block_dir_len = strlen (priv->block_dir);
priv->tmp_dir = g_strdup (tmp_dir);
priv->tmp_dir_len = strlen (tmp_dir);
if (g_mkdir_with_parents (priv->block_dir, 0777) < 0) {
seaf_warning ("Block dir %s does not exist and"
" is unable to create\n", priv->block_dir);
goto onerror;
}
if (g_mkdir_with_parents (tmp_dir, 0777) < 0) {
seaf_warning ("Blocks tmp dir %s does not exist and"
" is unable to create\n", tmp_dir);
goto onerror;
}
bend->open_block = block_backend_fs_open_block;
bend->read_block = block_backend_fs_read_block;
bend->write_block = block_backend_fs_write_block;
bend->commit_block = block_backend_fs_commit_block;
bend->close_block = block_backend_fs_close_block;
bend->exists = block_backend_fs_block_exists;
bend->remove_block = block_backend_fs_remove_block;
bend->stat_block = block_backend_fs_stat_block;
bend->stat_block_by_handle = block_backend_fs_stat_block_by_handle;
bend->block_handle_free = block_backend_fs_block_handle_free;
bend->foreach_block = block_backend_fs_foreach_block;
bend->remove_store = block_backend_fs_remove_store;
return bend;
onerror:
g_free (bend);
g_free (bend->be_priv);
return NULL;
}
seadrive-fuse-3.0.13/src/block-backend.c 0000664 0000000 0000000 00000002314 14761776747 0020004 0 ustar 00root root 0000000 0000000
#include "common.h"
#include "log.h"
#include "block-backend.h"
extern BlockBackend *
block_backend_fs_new (const char *block_dir, const char *tmp_dir);
BlockBackend*
load_filesystem_block_backend(GKeyFile *config)
{
BlockBackend *bend;
char *tmp_dir;
char *block_dir;
block_dir = g_key_file_get_string (config, "block_backend", "block_dir", NULL);
if (!block_dir) {
seaf_warning ("Block dir not set in config.\n");
return NULL;
}
tmp_dir = g_key_file_get_string (config, "block_backend", "tmp_dir", NULL);
if (!tmp_dir) {
seaf_warning ("Block tmp dir not set in config.\n");
return NULL;
}
bend = block_backend_fs_new (block_dir, tmp_dir);
g_free (block_dir);
g_free (tmp_dir);
return bend;
}
BlockBackend*
load_block_backend (GKeyFile *config)
{
char *backend;
BlockBackend *bend;
backend = g_key_file_get_string (config, "block_backend", "name", NULL);
if (!backend) {
return NULL;
}
if (strcmp(backend, "filesystem") == 0) {
bend = load_filesystem_block_backend(config);
g_free (backend);
return bend;
}
seaf_warning ("Unknown backend\n");
return NULL;
}
seadrive-fuse-3.0.13/src/block-backend.h 0000664 0000000 0000000 00000003602 14761776747 0020012 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#ifndef BLOCK_BACKEND_H
#define BLOCK_BACKEND_H
#include "block.h"
typedef struct BlockBackend BlockBackend;
struct BlockBackend {
BHandle* (*open_block) (BlockBackend *bend,
const char *store_id, int version,
const char *block_id, int rw_type);
int (*read_block) (BlockBackend *bend, BHandle *handle, void *buf, int len);
int (*write_block) (BlockBackend *bend, BHandle *handle, const void *buf, int len);
int (*commit_block) (BlockBackend *bend, BHandle *handle);
int (*close_block) (BlockBackend *bend, BHandle *handle);
int (*exists) (BlockBackend *bend,
const char *store_id, int version,
const char *block_id);
int (*remove_block) (BlockBackend *bend,
const char *store_id, int version,
const char *block_id);
BMetadata* (*stat_block) (BlockBackend *bend,
const char *store_id, int version,
const char *block_id);
BMetadata* (*stat_block_by_handle) (BlockBackend *bend, BHandle *handle);
void (*block_handle_free) (BlockBackend *bend, BHandle *handle);
int (*foreach_block) (BlockBackend *bend,
const char *store_id,
int version,
SeafBlockFunc process,
void *user_data);
/* Only valid for version 1 repo. Remove all blocks for the repo. */
int (*remove_store) (BlockBackend *bend,
const char *store_id);
void* be_priv; /* backend private field */
};
BlockBackend* load_block_backend (GKeyFile *config);
#endif
seadrive-fuse-3.0.13/src/block-mgr.c 0000664 0000000 0000000 00000015151 14761776747 0017205 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#include "common.h"
#include "seafile-session.h"
#include "utils.h"
#include "block-mgr.h"
#include "log.h"
#include
#include
#include
#include
#include
#include
#include "block-backend.h"
#define SEAF_BLOCK_DIR "blocks"
extern BlockBackend *
block_backend_fs_new (const char *block_dir, const char *tmp_dir);
SeafBlockManager *
seaf_block_manager_new (struct _SeafileSession *seaf,
const char *seaf_dir)
{
SeafBlockManager *mgr;
mgr = g_new0 (SeafBlockManager, 1);
mgr->seaf = seaf;
mgr->backend = block_backend_fs_new (seaf_dir, seaf->tmp_file_dir);
if (!mgr->backend) {
seaf_warning ("[Block mgr] Failed to load backend.\n");
goto onerror;
}
return mgr;
onerror:
g_free (mgr);
return NULL;
}
int
seaf_block_manager_init (SeafBlockManager *mgr)
{
return 0;
}
BlockHandle *
seaf_block_manager_open_block (SeafBlockManager *mgr,
const char *store_id,
int version,
const char *block_id,
int rw_type)
{
if (!store_id || !is_uuid_valid(store_id) ||
!block_id || !is_object_id_valid(block_id))
return NULL;
return mgr->backend->open_block (mgr->backend,
store_id, version,
block_id, rw_type);
}
int
seaf_block_manager_read_block (SeafBlockManager *mgr,
BlockHandle *handle,
void *buf, int len)
{
return mgr->backend->read_block (mgr->backend, handle, buf, len);
}
int
seaf_block_manager_write_block (SeafBlockManager *mgr,
BlockHandle *handle,
const void *buf, int len)
{
return mgr->backend->write_block (mgr->backend, handle, buf, len);
}
int
seaf_block_manager_close_block (SeafBlockManager *mgr,
BlockHandle *handle)
{
return mgr->backend->close_block (mgr->backend, handle);
}
void
seaf_block_manager_block_handle_free (SeafBlockManager *mgr,
BlockHandle *handle)
{
mgr->backend->block_handle_free (mgr->backend, handle);
}
int
seaf_block_manager_commit_block (SeafBlockManager *mgr,
BlockHandle *handle)
{
return mgr->backend->commit_block (mgr->backend, handle);
}
gboolean seaf_block_manager_block_exists (SeafBlockManager *mgr,
const char *store_id,
int version,
const char *block_id)
{
if (!store_id || !is_uuid_valid(store_id) ||
!block_id || !is_object_id_valid(block_id))
return FALSE;
return mgr->backend->exists (mgr->backend, store_id, version, block_id);
}
int
seaf_block_manager_remove_block (SeafBlockManager *mgr,
const char *store_id,
int version,
const char *block_id)
{
if (!store_id || !is_uuid_valid(store_id) ||
!block_id || !is_object_id_valid(block_id))
return -1;
return mgr->backend->remove_block (mgr->backend, store_id, version, block_id);
}
BlockMetadata *
seaf_block_manager_stat_block (SeafBlockManager *mgr,
const char *store_id,
int version,
const char *block_id)
{
if (!store_id || !is_uuid_valid(store_id) ||
!block_id || !is_object_id_valid(block_id))
return NULL;
return mgr->backend->stat_block (mgr->backend, store_id, version, block_id);
}
BlockMetadata *
seaf_block_manager_stat_block_by_handle (SeafBlockManager *mgr,
BlockHandle *handle)
{
return mgr->backend->stat_block_by_handle (mgr->backend, handle);
}
int
seaf_block_manager_foreach_block (SeafBlockManager *mgr,
const char *store_id,
int version,
SeafBlockFunc process,
void *user_data)
{
return mgr->backend->foreach_block (mgr->backend,
store_id, version,
process, user_data);
}
static gboolean
get_block_number (const char *store_id,
int version,
const char *block_id,
void *data)
{
guint64 *n_blocks = data;
++(*n_blocks);
return TRUE;
}
guint64
seaf_block_manager_get_block_number (SeafBlockManager *mgr,
const char *store_id,
int version)
{
guint64 n_blocks = 0;
seaf_block_manager_foreach_block (mgr, store_id, version,
get_block_number, &n_blocks);
return n_blocks;
}
gboolean
seaf_block_manager_verify_block (SeafBlockManager *mgr,
const char *store_id,
int version,
const char *block_id,
gboolean *io_error)
{
BlockHandle *h;
char buf[10240];
int n;
GChecksum *cs;
const char *check_id;
gboolean ret;
h = seaf_block_manager_open_block (mgr,
store_id, version,
block_id, BLOCK_READ);
if (!h) {
seaf_warning ("Failed to open block %s:%.8s.\n", store_id, block_id);
*io_error = TRUE;
return FALSE;
}
cs = g_checksum_new (G_CHECKSUM_SHA1);
while (1) {
n = seaf_block_manager_read_block (mgr, h, buf, sizeof(buf));
if (n < 0) {
seaf_warning ("Failed to read block %s:%.8s.\n", store_id, block_id);
*io_error = TRUE;
g_checksum_free (cs);
return FALSE;
}
if (n == 0)
break;
g_checksum_update (cs, (guchar *)buf, n);
}
seaf_block_manager_close_block (mgr, h);
seaf_block_manager_block_handle_free (mgr, h);
check_id = g_checksum_get_string (cs);
if (strcmp (check_id, block_id) == 0)
ret = TRUE;
else
ret = FALSE;
g_checksum_free (cs);
return ret;
}
int
seaf_block_manager_remove_store (SeafBlockManager *mgr,
const char *store_id)
{
return mgr->backend->remove_store (mgr->backend, store_id);
}
seadrive-fuse-3.0.13/src/block-mgr.h 0000664 0000000 0000000 00000010260 14761776747 0017206 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#ifndef SEAF_BLOCK_MGR_H
#define SEAF_BLOCK_MGR_H
#include
#include
#include
#include "block.h"
struct _SeafileSession;
typedef struct _SeafBlockManager SeafBlockManager;
struct _SeafBlockManager {
struct _SeafileSession *seaf;
struct BlockBackend *backend;
};
SeafBlockManager *
seaf_block_manager_new (struct _SeafileSession *seaf,
const char *seaf_dir);
/*
* Open a block for read or write.
*
* @store_id: id for the block store
* @version: data format version for the repo
* @block_id: ID of block.
* @rw_type: BLOCK_READ or BLOCK_WRITE.
* Returns: A handle for the block.
*/
BlockHandle *
seaf_block_manager_open_block (SeafBlockManager *mgr,
const char *store_id,
int version,
const char *block_id,
int rw_type);
/*
* Read data from a block.
* The semantics is similar to readn.
*
* @handle: Hanlde returned by seaf_block_manager_open_block().
* @buf: Data wuold be copied into this buf.
* @len: At most @len bytes would be read.
*
* Returns: the bytes read.
*/
int
seaf_block_manager_read_block (SeafBlockManager *mgr,
BlockHandle *handle,
void *buf, int len);
/*
* Write data to a block.
* The semantics is similar to writen.
*
* @handle: Hanlde returned by seaf_block_manager_open_block().
* @buf: Data to be written to the block.
* @len: At most @len bytes would be written.
*
* Returns: the bytes written.
*/
int
seaf_block_manager_write_block (SeafBlockManager *mgr,
BlockHandle *handle,
const void *buf, int len);
/*
* Commit a block to storage.
* The block must be opened for write.
*
* @handle: Hanlde returned by seaf_block_manager_open_block().
*
* Returns: 0 on success, -1 on error.
*/
int
seaf_block_manager_commit_block (SeafBlockManager *mgr,
BlockHandle *handle);
/*
* Close an open block.
*
* @handle: Hanlde returned by seaf_block_manager_open_block().
*
* Returns: 0 on success, -1 on error.
*/
int
seaf_block_manager_close_block (SeafBlockManager *mgr,
BlockHandle *handle);
void
seaf_block_manager_block_handle_free (SeafBlockManager *mgr,
BlockHandle *handle);
gboolean
seaf_block_manager_block_exists (SeafBlockManager *mgr,
const char *store_id,
int version,
const char *block_id);
int
seaf_block_manager_remove_block (SeafBlockManager *mgr,
const char *store_id,
int version,
const char *block_id);
BlockMetadata *
seaf_block_manager_stat_block (SeafBlockManager *mgr,
const char *store_id,
int version,
const char *block_id);
BlockMetadata *
seaf_block_manager_stat_block_by_handle (SeafBlockManager *mgr,
BlockHandle *handle);
int
seaf_block_manager_foreach_block (SeafBlockManager *mgr,
const char *store_id,
int version,
SeafBlockFunc process,
void *user_data);
/* Remove all blocks for a repo. Only valid for version 1 repo. */
int
seaf_block_manager_remove_store (SeafBlockManager *mgr,
const char *store_id);
guint64
seaf_block_manager_get_block_number (SeafBlockManager *mgr,
const char *store_id,
int version);
gboolean
seaf_block_manager_verify_block (SeafBlockManager *mgr,
const char *store_id,
int version,
const char *block_id,
gboolean *io_error);
#endif
seadrive-fuse-3.0.13/src/block.h 0000664 0000000 0000000 00000001163 14761776747 0016425 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#ifndef BLOCK_H
#define BLOCK_H
typedef struct _BMetadata BlockMetadata;
typedef struct _BMetadata BMetadata;
struct _BMetadata {
char id[41];
uint32_t size;
};
/* Opaque block handle.
*/
typedef struct _BHandle BlockHandle;
typedef struct _BHandle BHandle;
enum {
BLOCK_READ,
BLOCK_WRITE,
};
typedef gboolean (*SeafBlockFunc) (const char *store_id,
int version,
const char *block_id,
void *user_data);
#endif
seadrive-fuse-3.0.13/src/branch-mgr.c 0000664 0000000 0000000 00000021276 14761776747 0017355 0 ustar 00root root 0000000 0000000 #include "common.h"
#include "log.h"
#include "db.h"
#include "seafile-session.h"
#include "branch-mgr.h"
#define BRANCH_DB "branch.db"
SeafBranch *
seaf_branch_new (const char *name,
const char *repo_id,
const char *commit_id,
gint64 opid)
{
SeafBranch *branch;
branch = g_new0 (SeafBranch, 1);
branch->name = g_strdup (name);
memcpy (branch->repo_id, repo_id, 36);
branch->repo_id[36] = '\0';
memcpy (branch->commit_id, commit_id, 40);
branch->commit_id[40] = '\0';
branch->opid = opid;
branch->ref = 1;
return branch;
}
void
seaf_branch_free (SeafBranch *branch)
{
if (branch == NULL) return;
g_free (branch->name);
g_free (branch);
}
void
seaf_branch_list_free (GList *blist)
{
GList *ptr;
for (ptr = blist; ptr; ptr = ptr->next) {
seaf_branch_unref (ptr->data);
}
g_list_free (blist);
}
void
seaf_branch_set_commit (SeafBranch *branch, const char *commit_id)
{
memcpy (branch->commit_id, commit_id, 40);
branch->commit_id[40] = '\0';
}
void
seaf_branch_ref (SeafBranch *branch)
{
branch->ref++;
}
void
seaf_branch_unref (SeafBranch *branch)
{
if (!branch)
return;
if (--branch->ref <= 0)
seaf_branch_free (branch);
}
struct _SeafBranchManagerPriv {
sqlite3 *db;
pthread_mutex_t db_lock;
};
static int open_db (SeafBranchManager *mgr);
SeafBranchManager *
seaf_branch_manager_new (struct _SeafileSession *seaf)
{
SeafBranchManager *mgr;
mgr = g_new0 (SeafBranchManager, 1);
mgr->priv = g_new0 (SeafBranchManagerPriv, 1);
mgr->seaf = seaf;
pthread_mutex_init (&mgr->priv->db_lock, NULL);
return mgr;
}
int
seaf_branch_manager_init (SeafBranchManager *mgr)
{
return open_db (mgr);
}
static int
open_db (SeafBranchManager *mgr)
{
char *db_path;
const char *sql;
db_path = g_build_filename (mgr->seaf->seaf_dir, BRANCH_DB, NULL);
if (sqlite_open_db (db_path, &mgr->priv->db) < 0) {
g_critical ("[Branch mgr] Failed to open branch db\n");
g_free (db_path);
return -1;
}
g_free (db_path);
sql = "CREATE TABLE IF NOT EXISTS Branch ("
"name TEXT, repo_id TEXT, commit_id TEXT, opid INTEGER);";
if (sqlite_query_exec (mgr->priv->db, sql) < 0)
return -1;
sql = "CREATE INDEX IF NOT EXISTS branch_index ON Branch(repo_id, name);";
if (sqlite_query_exec (mgr->priv->db, sql) < 0)
return -1;
return 0;
}
int
seaf_branch_manager_add_branch (SeafBranchManager *mgr, SeafBranch *branch)
{
char sql[256];
pthread_mutex_lock (&mgr->priv->db_lock);
sqlite3_snprintf (sizeof(sql), sql,
"SELECT 1 FROM Branch WHERE name=%Q and repo_id=%Q",
branch->name, branch->repo_id);
if (sqlite_check_for_existence (mgr->priv->db, sql))
sqlite3_snprintf (sizeof(sql), sql,
"UPDATE Branch SET commit_id=%Q, opid=%lld"
" WHERE name=%Q and repo_id=%Q",
branch->commit_id, branch->opid, branch->name, branch->repo_id);
else
sqlite3_snprintf (sizeof(sql), sql,
"INSERT INTO Branch (name, repo_id, commit_id, opid) "
"VALUES (%Q, %Q, %Q, %lld)",
branch->name, branch->repo_id, branch->commit_id, branch->opid);
sqlite_query_exec (mgr->priv->db, sql);
pthread_mutex_unlock (&mgr->priv->db_lock);
return 0;
}
int
seaf_branch_manager_del_branch (SeafBranchManager *mgr,
const char *repo_id,
const char *name)
{
char *sql;
pthread_mutex_lock (&mgr->priv->db_lock);
sql = sqlite3_mprintf ("DELETE FROM Branch WHERE name = %Q AND "
"repo_id = %Q", name, repo_id);
if (sqlite_query_exec (mgr->priv->db, sql) < 0)
seaf_warning ("Delete branch %s failed\n", name);
sqlite3_free (sql);
pthread_mutex_unlock (&mgr->priv->db_lock);
return 0;
}
int
seaf_branch_manager_update_branch (SeafBranchManager *mgr, SeafBranch *branch)
{
sqlite3 *db;
char *sql;
pthread_mutex_lock (&mgr->priv->db_lock);
db = mgr->priv->db;
sql = sqlite3_mprintf ("UPDATE Branch SET commit_id = %Q, opid = %lld"
" WHERE name = %Q AND repo_id = %Q",
branch->commit_id, branch->opid, branch->name, branch->repo_id);
sqlite_query_exec (db, sql);
sqlite3_free (sql);
pthread_mutex_unlock (&mgr->priv->db_lock);
return 0;
}
int
seaf_branch_manager_update_repo_branches (SeafBranchManager *mgr, SeafBranch *branch_info)
{
sqlite3 *db;
char *sql;
pthread_mutex_lock (&mgr->priv->db_lock);
db = mgr->priv->db;
sql = sqlite3_mprintf ("UPDATE Branch SET commit_id = %Q, opid = %lld"
" WHERE repo_id = %Q",
branch_info->commit_id, branch_info->opid, branch_info->repo_id);
sqlite_query_exec (db, sql);
sqlite3_free (sql);
pthread_mutex_unlock (&mgr->priv->db_lock);
return 0;
}
static gboolean
get_branch_cb (sqlite3_stmt *stmt, void *vdata)
{
SeafBranch **pbranch = vdata;
const char *name, *repo_id, *commit_id;
gint64 opid;
name = (const char *)sqlite3_column_text (stmt, 0);
repo_id = (const char *)sqlite3_column_text (stmt, 1);
commit_id = (const char *)sqlite3_column_text (stmt, 2);
opid = (gint64)sqlite3_column_int64 (stmt, 3);
*pbranch = seaf_branch_new (name, repo_id, commit_id, opid);
return FALSE;
}
SeafBranch *
seaf_branch_manager_get_branch (SeafBranchManager *mgr,
const char *repo_id,
const char *name)
{
SeafBranch *branch = NULL;
sqlite3 *db;
char *sql;
pthread_mutex_lock (&mgr->priv->db_lock);
db = mgr->priv->db;
sql = sqlite3_mprintf ("SELECT name, repo_id, commit_id, opid FROM Branch "
"WHERE name = %Q and repo_id=%Q",
name, repo_id);
if (sqlite_foreach_selected_row (db, sql, get_branch_cb, &branch) < 0) {
seaf_warning ("Failed to get branch %s of repo %s.\n", name, repo_id);
branch = NULL;
}
sqlite3_free (sql);
pthread_mutex_unlock (&mgr->priv->db_lock);
return branch;
}
gboolean
seaf_branch_manager_branch_exists (SeafBranchManager *mgr,
const char *repo_id,
const char *name)
{
char *sql;
gboolean ret;
pthread_mutex_lock (&mgr->priv->db_lock);
sql = sqlite3_mprintf ("SELECT name FROM Branch WHERE name = %Q "
"AND repo_id=%Q", name, repo_id);
ret = sqlite_check_for_existence (mgr->priv->db, sql);
sqlite3_free (sql);
pthread_mutex_unlock (&mgr->priv->db_lock);
return ret;
}
static gboolean
get_branch_list_cb (sqlite3_stmt *stmt, void *vdata)
{
GList **pret = vdata;
SeafBranch *branch;
const char *name, *repo_id, *commit_id;
gint64 opid;
name = (const char *)sqlite3_column_text (stmt, 0);
repo_id = (const char *)sqlite3_column_text (stmt, 1);
commit_id = (const char *)sqlite3_column_text (stmt, 2);
opid = (gint64)sqlite3_column_int64 (stmt, 3);
branch = seaf_branch_new (name, repo_id, commit_id, opid);
*pret = g_list_prepend (*pret, branch);
return TRUE;
}
GList *
seaf_branch_manager_get_branch_list (SeafBranchManager *mgr,
const char *repo_id)
{
sqlite3 *db = mgr->priv->db;
char *sql;
GList *ret = NULL;
sql = sqlite3_mprintf ("SELECT name, repo_id, commit_id, opid "
"FROM branch WHERE repo_id =%Q",
repo_id);
pthread_mutex_lock (&mgr->priv->db_lock);
if (sqlite_foreach_selected_row (db, sql, get_branch_list_cb, &ret) < 0) {
seaf_warning ("Failed to get branch list of repo %s.\n", repo_id);
g_list_free_full (ret, (GDestroyNotify)seaf_branch_free);
}
pthread_mutex_unlock (&mgr->priv->db_lock);
sqlite3_free (sql);
return g_list_reverse(ret);
}
int
seaf_branch_manager_update_opid (SeafBranchManager *mgr,
const char *repo_id,
const char *name,
gint64 opid)
{
sqlite3 *db;
char *sql;
pthread_mutex_lock (&mgr->priv->db_lock);
db = mgr->priv->db;
sql = sqlite3_mprintf ("UPDATE Branch SET opid = %lld"
" WHERE name = %Q AND repo_id = %Q",
opid, name, repo_id);
sqlite_query_exec (db, sql);
sqlite3_free (sql);
pthread_mutex_unlock (&mgr->priv->db_lock);
return 0;
}
seadrive-fuse-3.0.13/src/branch-mgr.h 0000664 0000000 0000000 00000004474 14761776747 0017363 0 ustar 00root root 0000000 0000000 #ifndef SEAF_BRANCH_MGR_H
#define SEAF_BRANCH_MGR_H
#include "commit-mgr.h"
#define NO_BRANCH "-"
typedef struct _SeafBranch SeafBranch;
struct _SeafBranch {
int ref;
char *name;
char repo_id[37];
char commit_id[41];
gint64 opid;
};
SeafBranch *seaf_branch_new (const char *name,
const char *repo_id,
const char *commit_id,
gint64 opid);
void seaf_branch_free (SeafBranch *branch);
void seaf_branch_set_commit (SeafBranch *branch, const char *commit_id);
void seaf_branch_ref (SeafBranch *branch);
void seaf_branch_unref (SeafBranch *branch);
typedef struct _SeafBranchManager SeafBranchManager;
typedef struct _SeafBranchManagerPriv SeafBranchManagerPriv;
struct _SeafileSession;
struct _SeafBranchManager {
struct _SeafileSession *seaf;
SeafBranchManagerPriv *priv;
};
SeafBranchManager *seaf_branch_manager_new (struct _SeafileSession *seaf);
int seaf_branch_manager_init (SeafBranchManager *mgr);
int
seaf_branch_manager_add_branch (SeafBranchManager *mgr, SeafBranch *branch);
int
seaf_branch_manager_del_branch (SeafBranchManager *mgr,
const char *repo_id,
const char *name);
void
seaf_branch_list_free (GList *blist);
int
seaf_branch_manager_update_branch (SeafBranchManager *mgr,
SeafBranch *branch);
/* Update 'local' and 'master' branch together. */
int
seaf_branch_manager_update_repo_branches (SeafBranchManager *mgr,
SeafBranch *new_branch_info);
SeafBranch *
seaf_branch_manager_get_branch (SeafBranchManager *mgr,
const char *repo_id,
const char *name);
gboolean
seaf_branch_manager_branch_exists (SeafBranchManager *mgr,
const char *repo_id,
const char *name);
GList *
seaf_branch_manager_get_branch_list (SeafBranchManager *mgr,
const char *repo_id);
int
seaf_branch_manager_update_opid (SeafBranchManager *mgr,
const char *repo_id,
const char *name,
gint64 opid);
#endif /* SEAF_BRANCH_MGR_H */
seadrive-fuse-3.0.13/src/cdc.c 0000664 0000000 0000000 00000017723 14761776747 0016070 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#include "common.h"
#include "log.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include "utils.h"
#include "cdc.h"
#include "seafile-crypt.h"
#include "rabin-checksum.h"
#define finger rabin_checksum
#define rolling_finger rabin_rolling_checksum
#define BLOCK_SZ (1024*1024*1)
#define BLOCK_MIN_SZ (1024*256)
#define BLOCK_MAX_SZ (1024*1024*4)
#define BLOCK_WIN_SZ 48
#define NAME_MAX_SZ 4096
#define BREAK_VALUE 0x0013 ///0x0513
#define READ_SIZE 1024 * 4
#define BYTE_TO_HEX(b) (((b)>=10)?('a'+b-10):('0'+b))
static int default_write_chunk (CDCDescriptor *chunk_descr)
{
char filename[NAME_MAX_SZ];
char chksum_str[CHECKSUM_LENGTH *2 + 1];
int fd_chunk, ret;
memset(chksum_str, 0, sizeof(chksum_str));
rawdata_to_hex (chunk_descr->checksum, chksum_str, CHECKSUM_LENGTH);
snprintf (filename, NAME_MAX_SZ, "./%s", chksum_str);
fd_chunk = g_open (filename, O_RDWR | O_CREAT | O_BINARY, 0644);
if (fd_chunk < 0)
return -1;
ret = writen (fd_chunk, chunk_descr->block_buf, chunk_descr->len);
close (fd_chunk);
return ret;
}
static int init_cdc_file_descriptor (int fd,
uint64_t file_size,
CDCFileDescriptor *file_descr)
{
uint32_t max_block_nr = 0;
uint32_t block_min_sz = 0;
file_descr->block_nr = 0;
if (file_descr->block_min_sz <= 0)
file_descr->block_min_sz = BLOCK_MIN_SZ;
if (file_descr->block_max_sz <= 0)
file_descr->block_max_sz = BLOCK_MAX_SZ;
if (file_descr->block_sz <= 0)
file_descr->block_sz = BLOCK_SZ;
if (file_descr->write_block == NULL)
file_descr->write_block = (WriteblockFunc)default_write_chunk;
block_min_sz = file_descr->block_min_sz;
max_block_nr = ((file_size + block_min_sz - 1) / block_min_sz);
file_descr->blk_sha1s = (uint8_t *)calloc (sizeof(uint8_t),
max_block_nr * CHECKSUM_LENGTH);
file_descr->max_block_nr = max_block_nr;
return 0;
}
#define WRITE_CDC_BLOCK(block_sz, write_data) \
do { \
int _block_sz = (block_sz); \
chunk_descr.len = _block_sz; \
chunk_descr.offset = offset; \
ret = file_descr->write_block (file_descr->repo_id, \
file_descr->version, \
&chunk_descr, \
crypt, chunk_descr.checksum, \
(write_data)); \
if (ret < 0) { \
free (buf); \
g_warning ("CDC: failed to write chunk.\n"); \
return -1; \
} \
memcpy (file_descr->blk_sha1s + \
file_descr->block_nr * CHECKSUM_LENGTH, \
chunk_descr.checksum, CHECKSUM_LENGTH); \
g_checksum_update (file_ctx, chunk_descr.checksum, 20); \
file_descr->block_nr++; \
offset += _block_sz; \
\
memmove (buf, buf + _block_sz, tail - _block_sz); \
tail = tail - _block_sz; \
cur = 0; \
}while(0);
/* content-defined chunking */
int file_chunk_cdc(int fd_src,
CDCFileDescriptor *file_descr,
SeafileCrypt *crypt,
gboolean write_data)
{
char *buf = NULL;
uint32_t buf_sz;
unsigned int len;
GChecksum *file_ctx = g_checksum_new (G_CHECKSUM_SHA1);
CDCDescriptor chunk_descr;
int ret = 0;
SeafStat sb;
if (seaf_fstat (fd_src, &sb) < 0) {
seaf_warning ("CDC: failed to stat: %s.\n", strerror(errno));
ret = -1;
goto out;
}
uint64_t expected_size = sb.st_size;
init_cdc_file_descriptor (fd_src, expected_size, file_descr);
uint32_t block_min_sz = file_descr->block_min_sz;
uint32_t block_mask = file_descr->block_sz - 1;
int fingerprint = 0;
int offset = 0;
int tail, cur, rsize;
buf_sz = file_descr->block_max_sz;
buf = chunk_descr.block_buf = malloc (buf_sz);
if (!buf) {
ret = -1;
goto out;
}
/* buf: a fix-sized buffer.
* cur: data behind (inclusive) this offset has been scanned.
* cur + 1 is the bytes that has been scanned.
* tail: length of data loaded into memory. buf[tail] is invalid.
*/
tail = cur = 0;
while (1) {
if (tail < block_min_sz) {
rsize = block_min_sz - tail + READ_SIZE;
} else {
rsize = (buf_sz - tail < READ_SIZE) ? (buf_sz - tail) : READ_SIZE;
}
ret = readn (fd_src, buf + tail, rsize);
if (ret < 0) {
seaf_warning ("CDC: failed to read: %s.\n", strerror(errno));
ret = -1;
goto out;
}
tail += ret;
file_descr->file_size += ret;
if (file_descr->file_size > expected_size) {
seaf_warning ("File size changed while chunking.\n");
ret = -1;
goto out;
}
/* We've read all the data in this file. Output the block immediately
* in two cases:
* 1. The data left in the file is less than block_min_sz;
* 2. We cannot find the break value until the end of this file.
*/
if (tail < block_min_sz || cur >= tail) {
if (tail > 0) {
if (file_descr->block_nr == file_descr->max_block_nr) {
seaf_warning ("Block id array is not large enough, bail out.\n");
ret = -1;
goto out;
}
WRITE_CDC_BLOCK (tail, write_data);
}
break;
}
/*
* A block is at least of size block_min_sz.
*/
if (cur < block_min_sz - 1)
cur = block_min_sz - 1;
while (cur < tail) {
fingerprint = (cur == block_min_sz - 1) ?
finger(buf + cur - BLOCK_WIN_SZ + 1, BLOCK_WIN_SZ) :
rolling_finger (fingerprint, BLOCK_WIN_SZ,
*(buf+cur-BLOCK_WIN_SZ), *(buf + cur));
/* get a chunk, write block info to chunk file */
if (((fingerprint & block_mask) == ((BREAK_VALUE & block_mask)))
|| cur + 1 >= file_descr->block_max_sz)
{
if (file_descr->block_nr == file_descr->max_block_nr) {
seaf_warning ("Block id array is not large enough, bail out.\n");
ret = -1;
goto out;
}
WRITE_CDC_BLOCK (cur + 1, write_data);
break;
} else {
cur ++;
}
}
}
gsize chk_sum_len = CHECKSUM_LENGTH;
g_checksum_get_digest (file_ctx, file_descr->file_sum, &chk_sum_len);
out:
free (buf);
g_checksum_free (file_ctx);
return ret;
}
int filename_chunk_cdc(const char *filename,
CDCFileDescriptor *file_descr,
SeafileCrypt *crypt,
gboolean write_data)
{
int fd_src = seaf_util_open (filename, O_RDONLY | O_BINARY);
if (fd_src < 0) {
seaf_warning ("CDC: failed to open %s.\n", filename);
return -1;
}
int ret = file_chunk_cdc (fd_src, file_descr, crypt, write_data);
close (fd_src);
return ret;
}
void cdc_init ()
{
rabin_init (BLOCK_WIN_SZ);
}
seadrive-fuse-3.0.13/src/cdc.h 0000664 0000000 0000000 00000003040 14761776747 0016060 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#ifndef _CDC_H
#define _CDC_H
#include
#include
#define CHECKSUM_LENGTH 20
#ifndef O_BINARY
#define O_BINARY 0
#endif
struct _CDCFileDescriptor;
struct _CDCDescriptor;
struct SeafileCrypt;
typedef int (*WriteblockFunc)(const char *repo_id,
int version,
struct _CDCDescriptor *chunk_descr,
struct SeafileCrypt *crypt,
uint8_t *checksum,
gboolean write_data);
/* define chunk file header and block entry */
typedef struct _CDCFileDescriptor {
uint32_t block_min_sz;
uint32_t block_max_sz;
uint32_t block_sz;
uint64_t file_size;
uint32_t block_nr;
uint8_t *blk_sha1s;
uint32_t max_block_nr;
uint8_t file_sum[CHECKSUM_LENGTH];
WriteblockFunc write_block;
char repo_id[37];
int version;
} CDCFileDescriptor;
typedef struct _CDCDescriptor {
uint64_t offset;
uint32_t len;
uint8_t checksum[CHECKSUM_LENGTH];
char *block_buf;
int result;
} CDCDescriptor;
int file_chunk_cdc(int fd_src,
CDCFileDescriptor *file_descr,
struct SeafileCrypt *crypt,
gboolean write_data);
int filename_chunk_cdc(const char *filename,
CDCFileDescriptor *file_descr,
struct SeafileCrypt *crypt,
gboolean write_data);
void cdc_init ();
#endif
seadrive-fuse-3.0.13/src/change-set.c 0000664 0000000 0000000 00000042503 14761776747 0017347 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#include "common.h"
#include "seafile-session.h"
#include "utils.h"
#include "log.h"
#include "diff-simple.h"
#include "change-set.h"
struct _ChangeSetDir {
int version;
char dir_id[41];
/* A hash table of dirents for fast lookup and insertion. */
GHashTable *dents;
};
typedef struct _ChangeSetDir ChangeSetDir;
struct _ChangeSetDirent {
guint32 mode;
char id[41];
char *name;
gint64 mtime;
char *modifier;
gint64 size;
/* Only used for directory. Most of time this is NULL
* unless we change the subdir too.
*/
ChangeSetDir *subdir;
};
typedef struct _ChangeSetDirent ChangeSetDirent;
/* Change set dirent. */
static ChangeSetDirent *
changeset_dirent_new (const char *id, guint32 mode, const char *name,
gint64 mtime, const char *modifier, gint64 size)
{
ChangeSetDirent *dent = g_new0 (ChangeSetDirent, 1);
dent->mode = mode;
memcpy (dent->id, id, 40);
dent->name = g_strdup(name);
dent->mtime = mtime;
dent->modifier = g_strdup(modifier);
dent->size = size;
return dent;
}
static ChangeSetDirent *
seaf_dirent_to_changeset_dirent (SeafDirent *seaf_dent)
{
return changeset_dirent_new (seaf_dent->id, seaf_dent->mode, seaf_dent->name,
seaf_dent->mtime, seaf_dent->modifier, seaf_dent->size);
}
static SeafDirent *
changeset_dirent_to_seaf_dirent (int version, ChangeSetDirent *dent)
{
return seaf_dirent_new (version, dent->id, dent->mode, dent->name,
dent->mtime, dent->modifier, dent->size);
}
static void
changeset_dir_free (ChangeSetDir *dir);
static void
changeset_dirent_free (ChangeSetDirent *dent)
{
if (!dent)
return;
g_free (dent->name);
g_free (dent->modifier);
/* Recursively free subdir. */
if (dent->subdir)
changeset_dir_free (dent->subdir);
g_free (dent);
}
/* Change set dir. */
static void
add_dent_to_dir (ChangeSetDir *dir, ChangeSetDirent *dent)
{
g_hash_table_insert (dir->dents,
g_strdup(dent->name),
dent);
}
static void
remove_dent_from_dir (ChangeSetDir *dir, const char *dname)
{
char *key;
if (g_hash_table_lookup_extended (dir->dents, dname,
(gpointer*)&key, NULL)) {
g_hash_table_steal (dir->dents, dname);
g_free (key);
}
}
static ChangeSetDir *
changeset_dir_new (int version, const char *id, GList *dirents)
{
ChangeSetDir *dir = g_new0 (ChangeSetDir, 1);
GList *ptr;
SeafDirent *dent;
ChangeSetDirent *changeset_dent;
dir->version = version;
if (id)
memcpy (dir->dir_id, id, 40);
dir->dents = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, (GDestroyNotify)changeset_dirent_free);
for (ptr = dirents; ptr; ptr = ptr->next) {
dent = ptr->data;
changeset_dent = seaf_dirent_to_changeset_dirent(dent);
add_dent_to_dir (dir, changeset_dent);
}
return dir;
}
static void
changeset_dir_free (ChangeSetDir *dir)
{
if (!dir)
return;
g_hash_table_destroy (dir->dents);
g_free (dir);
}
static ChangeSetDir *
seaf_dir_to_changeset_dir (SeafDir *seaf_dir)
{
return changeset_dir_new (seaf_dir->version, seaf_dir->dir_id, seaf_dir->entries);
}
static gint
compare_dents (gconstpointer a, gconstpointer b)
{
const SeafDirent *denta = a, *dentb = b;
return strcmp(dentb->name, denta->name);
}
static SeafDir *
changeset_dir_to_seaf_dir (ChangeSetDir *dir)
{
GList *dents = NULL, *seaf_dents = NULL;
GList *ptr;
ChangeSetDirent *dent;
SeafDirent *seaf_dent;
SeafDir *seaf_dir;
dents = g_hash_table_get_values (dir->dents);
for (ptr = dents; ptr; ptr = ptr->next) {
dent = ptr->data;
seaf_dent = changeset_dirent_to_seaf_dirent (dir->version, dent);
seaf_dents = g_list_prepend (seaf_dents, seaf_dent);
}
/* Sort it in descending order. */
seaf_dents = g_list_sort (seaf_dents, compare_dents);
/* seaf_dir_new() computes the dir id. */
seaf_dir = seaf_dir_new (NULL, seaf_dents, dir->version);
g_list_free (dents);
return seaf_dir;
}
/* Change set. */
ChangeSet *
changeset_new (const char *repo_id)
{
SeafRepo *repo;
SeafCommit *commit = NULL;
SeafDir *seaf_dir = NULL;
ChangeSetDir *changeset_dir = NULL;
ChangeSet *changeset = NULL;
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo) {
seaf_warning ("Failed to find repo %s.\n", repo_id);
return NULL;
}
commit = seaf_commit_manager_get_commit (seaf->commit_mgr,
repo_id,
repo->version,
repo->head->commit_id);
if (!commit) {
seaf_warning ("Failed to find head commit %s for repo %s.\n",
repo->head->commit_id, repo_id);
goto out;
}
seaf_dir = seaf_fs_manager_get_seafdir_sorted (seaf->fs_mgr,
repo_id,
repo->version,
commit->root_id);
if (!seaf_dir) {
seaf_warning ("Failed to find root dir %s in repo %s\n",
repo->root_id, repo_id);
goto out;
}
changeset_dir = seaf_dir_to_changeset_dir (seaf_dir);
if (!changeset_dir)
goto out;
changeset = g_new0 (ChangeSet, 1);
memcpy (changeset->repo_id, repo_id, 36);
changeset->tree_root = changeset_dir;
out:
seaf_repo_unref (repo);
seaf_commit_unref (commit);
seaf_dir_free (seaf_dir);
return changeset;
}
void
changeset_free (ChangeSet *changeset)
{
if (!changeset)
return;
changeset_dir_free (changeset->tree_root);
g_free (changeset);
}
static void
update_file (ChangeSetDirent *dent,
unsigned char *sha1,
SeafStat *st,
const char *modifier)
{
if (!st || !S_ISREG(st->st_mode))
return;
if (dent->mode == create_mode (st->st_mode) &&
dent->mtime == st->st_mtime &&
dent->size == st->st_size)
return;
dent->mode = create_mode(st->st_mode);
dent->mtime = (gint64)st->st_mtime;
dent->size = (gint64)st->st_size;
if (sha1)
rawdata_to_hex (sha1, dent->id, 20);
g_free (dent->modifier);
dent->modifier = g_strdup(modifier);
}
static void
create_new_dent (ChangeSetDir *dir,
const char *dname,
unsigned char *sha1,
SeafStat *st,
const char *modifier,
ChangeSetDirent *in_new_dent)
{
if (in_new_dent) {
g_free (in_new_dent->name);
in_new_dent->name = g_strdup(dname);
add_dent_to_dir (dir, in_new_dent);
return;
}
if (!sha1 || !st || !modifier)
return;
char id[41];
rawdata_to_hex (sha1, id, 20);
ChangeSetDirent *new_dent;
new_dent = changeset_dirent_new (id, create_mode(st->st_mode), dname,
st->st_mtime, modifier, st->st_size);
add_dent_to_dir (dir, new_dent);
}
static ChangeSetDir *
create_intermediate_dir (ChangeSetDir *parent, const char *dname)
{
ChangeSetDirent *dent;
dent = changeset_dirent_new (EMPTY_SHA1, S_IFDIR, dname, 0, NULL, 0);
dent->subdir = changeset_dir_new (parent->version, EMPTY_SHA1, NULL);
add_dent_to_dir (parent, dent);
return dent->subdir;
}
static void
add_to_tree (ChangeSet *changeset,
unsigned char *sha1,
SeafStat *st,
const char *modifier,
const char *path,
ChangeSetDirent *new_dent)
{
char *repo_id = changeset->repo_id;
ChangeSetDir *root = changeset->tree_root;
char **parts, *dname;
int n, i;
ChangeSetDir *dir;
ChangeSetDirent *dent;
SeafDir *seaf_dir;
parts = g_strsplit (path, "/", 0);
n = g_strv_length(parts);
dir = root;
for (i = 0; i < n; i++) {
dname = parts[i];
dent = g_hash_table_lookup (dir->dents, dname);
if (dent) {
if (S_ISDIR(dent->mode)) {
if (i == (n-1))
/* Don't need to update empty dir */
break;
if (!dent->subdir) {
seaf_dir = seaf_fs_manager_get_seafdir(seaf->fs_mgr,
repo_id,
root->version,
dent->id);
if (!seaf_dir) {
seaf_warning ("Failed to load seafdir %s:%s\n",
repo_id, dent->id);
break;
}
dent->subdir = seaf_dir_to_changeset_dir (seaf_dir);
seaf_dir_free (seaf_dir);
}
dir = dent->subdir;
} else if (S_ISREG(dent->mode)) {
if (i == (n-1)) {
/* File exists, update it. */
update_file (dent, sha1, st, modifier);
break;
}
}
} else {
if (i == (n-1)) {
create_new_dent (dir, dname, sha1, st, modifier, new_dent);
} else {
dir = create_intermediate_dir (dir, dname);
}
}
}
g_strfreev (parts);
}
static ChangeSetDirent *
delete_from_tree (ChangeSet *changeset,
const char *path,
gboolean *parent_empty)
{
char *repo_id = changeset->repo_id;
ChangeSetDir *root = changeset->tree_root;
char **parts, *dname;
int n, i;
ChangeSetDir *dir;
ChangeSetDirent *dent, *ret = NULL;
SeafDir *seaf_dir;
*parent_empty = FALSE;
parts = g_strsplit (path, "/", 0);
n = g_strv_length(parts);
dir = root;
for (i = 0; i < n; i++) {
dname = parts[i];
dent = g_hash_table_lookup (dir->dents, dname);
if (!dent)
break;
if (S_ISDIR(dent->mode)) {
if (i == (n-1)) {
/* Remove from hash table without freeing dent. */
remove_dent_from_dir (dir, dname);
if (g_hash_table_size (dir->dents) == 0)
*parent_empty = TRUE;
ret = dent;
break;
}
if (!dent->subdir) {
seaf_dir = seaf_fs_manager_get_seafdir(seaf->fs_mgr,
repo_id,
root->version,
dent->id);
if (!seaf_dir) {
seaf_warning ("Failed to load seafdir %s:%s\n",
repo_id, dent->id);
break;
}
dent->subdir = seaf_dir_to_changeset_dir (seaf_dir);
seaf_dir_free (seaf_dir);
}
dir = dent->subdir;
} else if (S_ISREG(dent->mode)) {
if (i == (n-1)) {
/* Remove from hash table without freeing dent. */
remove_dent_from_dir (dir, dname);
if (g_hash_table_size (dir->dents) == 0)
*parent_empty = TRUE;
ret = dent;
break;
}
}
}
g_strfreev (parts);
return ret;
}
static void
apply_to_tree (ChangeSet *changeset,
char status,
unsigned char *sha1,
SeafStat *st,
const char *modifier,
const char *path,
const char *new_path)
{
ChangeSetDirent *dent, *dent_dst;
gboolean dummy;
switch (status) {
case DIFF_STATUS_ADDED:
case DIFF_STATUS_MODIFIED:
case DIFF_STATUS_DIR_ADDED:
add_to_tree (changeset, sha1, st, modifier, path, NULL);
break;
case DIFF_STATUS_RENAMED:
dent = delete_from_tree (changeset, path, &dummy);
if (!dent)
break;
dent_dst = delete_from_tree (changeset, new_path, &dummy);
changeset_dirent_free (dent_dst);
add_to_tree (changeset, NULL, NULL, NULL, new_path, dent);
break;
}
}
void
add_to_changeset (ChangeSet *changeset,
char status,
unsigned char *sha1,
SeafStat *st,
const char *modifier,
const char *path,
const char *new_path)
{
/* if (add_to_diff) { */
/* de = diff_entry_new (DIFF_TYPE_INDEX, status, allzero, path); */
/* changeset->diff = g_list_prepend (changeset->diff, de); */
/* } */
apply_to_tree (changeset,
status, sha1, st, modifier, path, new_path);
}
static void
remove_from_changeset_recursive (ChangeSet *changeset,
const char *path,
gboolean remove_parent,
const char *top_dir)
{
ChangeSetDirent *dent;
gboolean parent_empty = FALSE;
dent = delete_from_tree (changeset, path, &parent_empty);
changeset_dirent_free (dent);
if (remove_parent && parent_empty) {
char *parent = g_strdup(path);
char *slash = strrchr (parent, '/');
if (slash) {
*slash = '\0';
if (g_strcmp0 (top_dir, parent) != 0) {
/* Recursively remove parent dirs. */
remove_from_changeset_recursive (changeset,
parent,
remove_parent,
top_dir);
}
}
g_free (parent);
}
}
void
remove_from_changeset (ChangeSet *changeset,
const char *path,
gboolean remove_parent,
const char *top_dir)
{
/* if (add_to_diff) { */
/* de = diff_entry_new (DIFF_TYPE_INDEX, status, allzero, path); */
/* changeset->diff = g_list_prepend (changeset->diff, de); */
/* } */
remove_from_changeset_recursive (changeset, path, remove_parent, top_dir);
}
static char *
commit_tree_recursive (const char *repo_id, ChangeSetDir *dir, gint64 *new_mtime)
{
ChangeSetDirent *dent;
GHashTableIter iter;
gpointer key, value;
char *new_id;
gint64 subdir_new_mtime;
gint64 dir_mtime = 0;
SeafDir *seaf_dir;
char *ret = NULL;
g_hash_table_iter_init (&iter, dir->dents);
while (g_hash_table_iter_next (&iter, &key, &value)) {
dent = value;
if (dent->subdir) {
new_id = commit_tree_recursive (repo_id, dent->subdir, &subdir_new_mtime);
if (!new_id)
return NULL;
memcpy (dent->id, new_id, 40);
dent->mtime = subdir_new_mtime;
g_free (new_id);
}
if (dir_mtime < dent->mtime)
dir_mtime = dent->mtime;
}
seaf_dir = changeset_dir_to_seaf_dir (dir);
memcpy (dir->dir_id, seaf_dir->dir_id, 40);
if (!seaf_fs_manager_object_exists (seaf->fs_mgr,
repo_id, dir->version,
seaf_dir->dir_id)) {
if (seaf_dir_save (seaf->fs_mgr, repo_id, dir->version, seaf_dir) < 0) {
seaf_warning ("Failed to save dir object %s to repo %s.\n",
seaf_dir->dir_id, repo_id);
goto out;
}
}
ret = g_strdup(seaf_dir->dir_id);
out:
if (ret != NULL)
*new_mtime = dir_mtime;
seaf_dir_free (seaf_dir);
return ret;
}
/*
* This function does two things:
* - calculate dir id from bottom up;
* - create and save seaf dir objects.
* It returns root dir id of the new commit.
*/
char *
commit_tree_from_changeset (ChangeSet *changeset)
{
gint64 mtime;
char *root_id = commit_tree_recursive (changeset->repo_id,
changeset->tree_root,
&mtime);
return root_id;
}
gboolean
changeset_check_path (ChangeSet *changeset,
const char *path,
gint64 size,
gint64 mtime)
{
ChangeSetDir *root = changeset->tree_root;
char **parts, *dname;
int n, i;
ChangeSetDir *dir;
ChangeSetDirent *dent;
gboolean ret = FALSE;
parts = g_strsplit (path, "/", 0);
n = g_strv_length(parts);
dir = root;
for (i = 0; i < n; i++) {
dname = parts[i];
dent = g_hash_table_lookup (dir->dents, dname);
if (!dent) {
break;
}
if (S_ISDIR(dent->mode)) {
if (i == (n-1)) {
// Always consider folders consistent
ret = TRUE;
break;
}
if (!dent->subdir) {
break;
}
dir = dent->subdir;
} else if (S_ISREG(dent->mode)) {
if (i == (n-1)) {
if (size == dent->size && mtime == dent->mtime)
ret = TRUE;
break;
}
// finding a file in the middle of a path is invalid
break;
}
}
g_strfreev (parts);
return ret;
}
seadrive-fuse-3.0.13/src/change-set.h 0000664 0000000 0000000 00000002407 14761776747 0017353 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#ifndef SEAF_CHANGE_SET_H
#define SEAF_CHANGE_SET_H
#include
#include "utils.h"
struct _ChangeSetDir;
struct _ChangeSet {
char repo_id[37];
/* A partial tree for all changed directories. */
struct _ChangeSetDir *tree_root;
};
typedef struct _ChangeSet ChangeSet;
ChangeSet *
changeset_new (const char *repo_id);
void
changeset_free (ChangeSet *changeset);
void
add_to_changeset (ChangeSet *changeset,
char status,
unsigned char *sha1,
SeafStat *st,
const char *modifier,
const char *path,
const char *new_path);
/*
@remove_parent: remove the parent dir when it becomes empty.
*/
void
remove_from_changeset (ChangeSet *changeset,
const char *path,
gboolean remove_parent,
const char *top_dir);
char *
commit_tree_from_changeset (ChangeSet *changeset);
// Check whether the information in changeset is consistent with the input
gboolean
changeset_check_path (ChangeSet *changeset,
const char *path,
gint64 size,
gint64 mtime);
#endif
seadrive-fuse-3.0.13/src/commit-mgr.c 0000664 0000000 0000000 00000066402 14761776747 0017410 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#include "common.h"
#include "log.h"
#include
#include "utils.h"
#include "db.h"
#include "seafile-session.h"
#include "commit-mgr.h"
#define MAX_TIME_SKEW 259200 /* 3 days */
struct _SeafCommitManagerPriv {
int dummy;
};
static SeafCommit *
load_commit (SeafCommitManager *mgr,
const char *repo_id, int version,
const char *commit_id);
static int
save_commit (SeafCommitManager *manager,
const char *repo_id, int version,
SeafCommit *commit);
static void
delete_commit (SeafCommitManager *mgr,
const char *repo_id, int version,
const char *id);
static json_t *
commit_to_json_object (SeafCommit *commit);
static SeafCommit *
commit_from_json_object (const char *id, json_t *object);
static void compute_commit_id (SeafCommit* commit)
{
GChecksum *ctx = g_checksum_new(G_CHECKSUM_SHA1);
uint8_t sha1[20];
gint64 ctime_n;
g_checksum_update (ctx, (guchar *)commit->root_id, 41);
g_checksum_update (ctx, (guchar *)commit->creator_id, 41);
if (commit->creator_name)
g_checksum_update (ctx, (guchar *)commit->creator_name, strlen(commit->creator_name)+1);
g_checksum_update (ctx, (guchar *)commit->desc, strlen(commit->desc)+1);
/* convert to network byte order */
ctime_n = hton64 (commit->ctime);
g_checksum_update (ctx, (guchar *)&ctime_n, sizeof(ctime_n));
gsize len = 20;
g_checksum_get_digest (ctx, sha1, &len);
rawdata_to_hex (sha1, commit->commit_id, 20);
g_checksum_free (ctx);
}
SeafCommit*
seaf_commit_new (const char *commit_id,
const char *repo_id,
const char *root_id,
const char *creator_name,
const char *creator_id,
const char *desc,
guint64 ctime)
{
SeafCommit *commit;
g_return_val_if_fail (repo_id != NULL, NULL);
g_return_val_if_fail (root_id != NULL && creator_id != NULL, NULL);
commit = g_new0 (SeafCommit, 1);
memcpy (commit->repo_id, repo_id, 36);
commit->repo_id[36] = '\0';
memcpy (commit->root_id, root_id, 40);
commit->root_id[40] = '\0';
commit->creator_name = g_strdup (creator_name);
memcpy (commit->creator_id, creator_id, 40);
commit->creator_id[40] = '\0';
commit->desc = g_strdup (desc);
if (ctime == 0) {
/* TODO: use more precise timer */
commit->ctime = (gint64)time(NULL);
} else
commit->ctime = ctime;
if (commit_id == NULL)
compute_commit_id (commit);
else {
memcpy (commit->commit_id, commit_id, 40);
commit->commit_id[40] = '\0';
}
commit->ref = 1;
return commit;
}
char *
seaf_commit_to_data (SeafCommit *commit, gsize *len)
{
json_t *object;
char *json_data;
char *ret;
object = commit_to_json_object (commit);
json_data = json_dumps (object, 0);
*len = strlen (json_data);
json_decref (object);
ret = g_strdup (json_data);
free (json_data);
return ret;
}
SeafCommit *
seaf_commit_from_data (const char *id, char *data, gsize len)
{
json_t *object;
SeafCommit *commit;
json_error_t jerror;
object = json_loadb (data, len, 0, &jerror);
if (!object) {
/* Perhaps the commit object contains invalid UTF-8 character. */
if (data[len-1] == 0)
clean_utf8_data (data, len - 1);
else
clean_utf8_data (data, len);
object = json_loadb (data, len, 0, &jerror);
if (!object) {
seaf_warning ("Failed to load commit json: %s.\n", jerror.text);
return NULL;
}
}
commit = commit_from_json_object (id, object);
json_decref (object);
return commit;
}
static void
seaf_commit_free (SeafCommit *commit)
{
g_free (commit->desc);
g_free (commit->creator_name);
if (commit->parent_id) g_free (commit->parent_id);
if (commit->second_parent_id) g_free (commit->second_parent_id);
if (commit->repo_name) g_free (commit->repo_name);
if (commit->repo_desc) g_free (commit->repo_desc);
if (commit->repo_category) g_free (commit->repo_category);
if (commit->device_name) g_free (commit->device_name);
g_free (commit->client_version);
g_free (commit->magic);
g_free (commit->random_key);
g_free (commit->pwd_hash);
g_free (commit->pwd_hash_algo);
g_free (commit->pwd_hash_params);
g_free (commit);
}
void
seaf_commit_ref (SeafCommit *commit)
{
commit->ref++;
}
void
seaf_commit_unref (SeafCommit *commit)
{
if (!commit)
return;
if (--commit->ref <= 0)
seaf_commit_free (commit);
}
SeafCommitManager*
seaf_commit_manager_new (SeafileSession *seaf)
{
SeafCommitManager *mgr = g_new0 (SeafCommitManager, 1);
mgr->priv = g_new0 (SeafCommitManagerPriv, 1);
mgr->seaf = seaf;
mgr->obj_store = seaf_obj_store_new (mgr->seaf, "commits");
return mgr;
}
int
seaf_commit_manager_init (SeafCommitManager *mgr)
{
if (seaf_obj_store_init (mgr->obj_store) < 0) {
seaf_warning ("[commit mgr] Failed to init commit object store.\n");
return -1;
}
return 0;
}
int
seaf_commit_manager_add_commit (SeafCommitManager *mgr,
SeafCommit *commit)
{
int ret;
/* add_commit_to_cache (mgr, commit); */
if ((ret = save_commit (mgr, commit->repo_id, commit->version, commit)) < 0)
return -1;
return 0;
}
void
seaf_commit_manager_del_commit (SeafCommitManager *mgr,
const char *repo_id,
int version,
const char *id)
{
g_return_if_fail (id != NULL);
delete_commit (mgr, repo_id, version, id);
}
SeafCommit*
seaf_commit_manager_get_commit (SeafCommitManager *mgr,
const char *repo_id,
int version,
const char *id)
{
SeafCommit *commit;
commit = load_commit (mgr, repo_id, version, id);
if (!commit)
return NULL;
/* add_commit_to_cache (mgr, commit); */
return commit;
}
SeafCommit *
seaf_commit_manager_get_commit_compatible (SeafCommitManager *mgr,
const char *repo_id,
const char *id)
{
SeafCommit *commit = NULL;
/* First try version 1 layout. */
commit = seaf_commit_manager_get_commit (mgr, repo_id, 1, id);
if (commit)
return commit;
/* For compatibility with version 0. */
commit = seaf_commit_manager_get_commit (mgr, repo_id, 0, id);
return commit;
}
static gint
compare_commit_by_time (gconstpointer a, gconstpointer b, gpointer unused)
{
const SeafCommit *commit_a = a;
const SeafCommit *commit_b = b;
/* Latest commit comes first in the list. */
return (commit_b->ctime - commit_a->ctime);
}
inline static int
insert_parent_commit (GList **list, GHashTable *hash,
const char *repo_id, int version,
const char *parent_id, gboolean allow_truncate)
{
SeafCommit *p;
char *key;
if (g_hash_table_lookup (hash, parent_id) != NULL)
return 0;
p = seaf_commit_manager_get_commit (seaf->commit_mgr,
repo_id, version,
parent_id);
if (!p) {
if (allow_truncate)
return 0;
seaf_warning ("Failed to find commit %s\n", parent_id);
return -1;
}
*list = g_list_insert_sorted_with_data (*list, p,
compare_commit_by_time,
NULL);
key = g_strdup (parent_id);
g_hash_table_replace (hash, key, key);
return 0;
}
gboolean
seaf_commit_manager_traverse_commit_tree_with_limit (SeafCommitManager *mgr,
const char *repo_id,
int version,
const char *head,
CommitTraverseFunc func,
int limit,
void *data,
gboolean skip_errors)
{
SeafCommit *commit;
GList *list = NULL;
GHashTable *commit_hash;
gboolean ret = TRUE;
/* A hash table for recording id of traversed commits. */
commit_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
commit = seaf_commit_manager_get_commit (mgr, repo_id, version, head);
if (!commit) {
seaf_warning ("Failed to find commit %s.\n", head);
return FALSE;
}
list = g_list_insert_sorted_with_data (list, commit,
compare_commit_by_time,
NULL);
char *key = g_strdup (commit->commit_id);
g_hash_table_replace (commit_hash, key, key);
int count = 0;
while (list) {
gboolean stop = FALSE;
commit = list->data;
list = g_list_delete_link (list, list);
if (!func (commit, data, &stop)) {
if (!skip_errors) {
seaf_commit_unref (commit);
ret = FALSE;
goto out;
}
}
/* Stop when limit is reached. If limit < 0, there is no limit; */
if (limit > 0 && ++count == limit) {
seaf_commit_unref (commit);
break;
}
if (stop) {
seaf_commit_unref (commit);
/* stop traverse down from this commit,
* but not stop traversing the tree
*/
continue;
}
if (commit->parent_id) {
if (insert_parent_commit (&list, commit_hash, repo_id, version,
commit->parent_id, FALSE) < 0) {
if (!skip_errors) {
seaf_commit_unref (commit);
ret = FALSE;
goto out;
}
}
}
if (commit->second_parent_id) {
if (insert_parent_commit (&list, commit_hash, repo_id, version,
commit->second_parent_id, FALSE) < 0) {
if (!skip_errors) {
seaf_commit_unref (commit);
ret = FALSE;
goto out;
}
}
}
seaf_commit_unref (commit);
}
out:
g_hash_table_destroy (commit_hash);
while (list) {
commit = list->data;
seaf_commit_unref (commit);
list = g_list_delete_link (list, list);
}
return ret;
}
static gboolean
traverse_commit_tree_common (SeafCommitManager *mgr,
const char *repo_id,
int version,
const char *head,
CommitTraverseFunc func,
void *data,
gboolean skip_errors,
gboolean allow_truncate)
{
SeafCommit *commit;
GList *list = NULL;
GHashTable *commit_hash;
gboolean ret = TRUE;
commit = seaf_commit_manager_get_commit (mgr, repo_id, version, head);
if (!commit) {
seaf_warning ("Failed to find commit %s.\n", head);
// For head commit corrupted, directly return FALSE
// user can repair head by fsck then retraverse the tree
return FALSE;
}
/* A hash table for recording id of traversed commits. */
commit_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
list = g_list_insert_sorted_with_data (list, commit,
compare_commit_by_time,
NULL);
char *key = g_strdup (commit->commit_id);
g_hash_table_replace (commit_hash, key, key);
while (list) {
gboolean stop = FALSE;
commit = list->data;
list = g_list_delete_link (list, list);
if (!func (commit, data, &stop)) {
seaf_warning("[comit-mgr] CommitTraverseFunc failed\n");
/* If skip errors, continue to traverse parents. */
if (!skip_errors) {
seaf_commit_unref (commit);
ret = FALSE;
goto out;
}
}
if (stop) {
seaf_commit_unref (commit);
/* stop traverse down from this commit,
* but not stop traversing the tree
*/
continue;
}
if (commit->parent_id) {
if (insert_parent_commit (&list, commit_hash, repo_id, version,
commit->parent_id, allow_truncate) < 0) {
seaf_warning("[comit-mgr] insert parent commit failed\n");
/* If skip errors, try insert second parent. */
if (!skip_errors) {
seaf_commit_unref (commit);
ret = FALSE;
goto out;
}
}
}
if (commit->second_parent_id) {
if (insert_parent_commit (&list, commit_hash, repo_id, version,
commit->second_parent_id, allow_truncate) < 0) {
seaf_warning("[comit-mgr]insert second parent commit failed\n");
if (!skip_errors) {
seaf_commit_unref (commit);
ret = FALSE;
goto out;
}
}
}
seaf_commit_unref (commit);
}
out:
g_hash_table_destroy (commit_hash);
while (list) {
commit = list->data;
seaf_commit_unref (commit);
list = g_list_delete_link (list, list);
}
return ret;
}
gboolean
seaf_commit_manager_traverse_commit_tree (SeafCommitManager *mgr,
const char *repo_id,
int version,
const char *head,
CommitTraverseFunc func,
void *data,
gboolean skip_errors)
{
return traverse_commit_tree_common (mgr, repo_id, version, head,
func, data, skip_errors, FALSE);
}
gboolean
seaf_commit_manager_traverse_commit_tree_truncated (SeafCommitManager *mgr,
const char *repo_id,
int version,
const char *head,
CommitTraverseFunc func,
void *data,
gboolean skip_errors)
{
return traverse_commit_tree_common (mgr, repo_id, version, head,
func, data, skip_errors, TRUE);
}
gboolean
seaf_commit_manager_commit_exists (SeafCommitManager *mgr,
const char *repo_id,
int version,
const char *id)
{
#if 0
commit = g_hash_table_lookup (mgr->priv->commit_cache, id);
if (commit != NULL)
return TRUE;
#endif
return seaf_obj_store_obj_exists (mgr->obj_store, repo_id, version, id);
}
inline static const char *json_object_get_string_or_null_member (json_t *object,const char *member_name)
{
json_t *ret = json_object_get (object, member_name);
if (ret)
return json_string_value(ret);
else
return NULL;
}
inline static void json_object_set_string_or_null_member (json_t *object,const char *member_name,const char *value)
{
if (value)
json_object_set_new(object,member_name, json_string(value));
else
json_object_set_new(object,member_name, json_null());
}
static json_t *
commit_to_json_object (SeafCommit *commit)
{
json_t *object;
object = json_object ();
json_object_set_string_member (object, "commit_id", commit->commit_id);
json_object_set_string_member (object, "root_id", commit->root_id);
json_object_set_string_member (object, "repo_id", commit->repo_id);
if (commit->creator_name)
json_object_set_string_member (object, "creator_name", commit->creator_name);
json_object_set_string_member (object, "creator", commit->creator_id);
json_object_set_string_member (object, "description", commit->desc);
json_object_set_int_member (object, "ctime", (gint64)commit->ctime);
json_object_set_string_or_null_member (object, "parent_id", commit->parent_id);
json_object_set_string_or_null_member (object, "second_parent_id",
commit->second_parent_id);
/*
* also save repo's properties to commit file, for easy sharing of
* repo info
*/
json_object_set_string_member (object, "repo_name", commit->repo_name);
json_object_set_string_member (object, "repo_desc",
commit->repo_desc);
json_object_set_string_or_null_member (object, "repo_category",
commit->repo_category);
if (commit->device_name)
json_object_set_string_member (object, "device_name", commit->device_name);
if (commit->client_version)
json_object_set_string_member (object, "client_version", commit->client_version);
if (commit->encrypted)
json_object_set_string_member (object, "encrypted", "true");
if (commit->encrypted) {
json_object_set_int_member (object, "enc_version", commit->enc_version);
if (commit->enc_version >= 1 && !commit->pwd_hash)
json_object_set_string_member (object, "magic", commit->magic);
if (commit->enc_version >= 2)
json_object_set_string_member (object, "key", commit->random_key);
if (commit->enc_version >= 3)
json_object_set_string_member (object, "salt", commit->salt);
if (commit->pwd_hash) {
json_object_set_string_member (object, "pwd_hash", commit->pwd_hash);
json_object_set_string_member (object, "pwd_hash_algo", commit->pwd_hash_algo);
json_object_set_string_member (object, "pwd_hash_params", commit->pwd_hash_params);
}
}
if (commit->version != 0)
json_object_set_int_member (object, "version", commit->version);
if (commit->conflict)
json_object_set_int_member (object, "conflict", 1);
if (commit->new_merge)
json_object_set_int_member (object, "new_merge", 1);
if (commit->repaired)
json_object_set_int_member (object, "repaired", 1);
return object;
}
static SeafCommit *
commit_from_json_object (const char *commit_id, json_t *object)
{
SeafCommit *commit = NULL;
const char *root_id;
const char *repo_id;
const char *creator_name = NULL;
const char *creator;
const char *desc;
gint64 ctime;
const char *parent_id, *second_parent_id;
const char *repo_name;
const char *repo_desc;
const char *repo_category;
const char *device_name;
const char *client_version;
const char *encrypted = NULL;
int enc_version = 0;
const char *magic = NULL;
const char *random_key = NULL;
const char *salt = NULL;
const char *pwd_hash = NULL;
const char *pwd_hash_algo = NULL;
const char *pwd_hash_params = NULL;
int version = 0;
int conflict = 0, new_merge = 0;
int repaired = 0;
root_id = json_object_get_string_member (object, "root_id");
repo_id = json_object_get_string_member (object, "repo_id");
if (json_object_has_member (object, "creator_name"))
creator_name = json_object_get_string_or_null_member (object, "creator_name");
creator = json_object_get_string_member (object, "creator");
desc = json_object_get_string_member (object, "description");
if (!desc)
desc = "";
ctime = (guint64) json_object_get_int_member (object, "ctime");
parent_id = json_object_get_string_or_null_member (object, "parent_id");
second_parent_id = json_object_get_string_or_null_member (object, "second_parent_id");
repo_name = json_object_get_string_member (object, "repo_name");
if (!repo_name)
repo_name = "";
repo_desc = json_object_get_string_member (object, "repo_desc");
if (!repo_desc)
repo_desc = "";
repo_category = json_object_get_string_or_null_member (object, "repo_category");
device_name = json_object_get_string_or_null_member (object, "device_name");
client_version = json_object_get_string_or_null_member (object, "client_version");
if (json_object_has_member (object, "encrypted"))
encrypted = json_object_get_string_or_null_member (object, "encrypted");
if (encrypted && strcmp(encrypted, "true") == 0
&& json_object_has_member (object, "enc_version")) {
enc_version = json_object_get_int_member (object, "enc_version");
magic = json_object_get_string_member (object, "magic");
pwd_hash = json_object_get_string_member (object, "pwd_hash");
pwd_hash_algo = json_object_get_string_member (object, "pwd_hash_algo");
pwd_hash_params = json_object_get_string_member (object, "pwd_hash_params");
}
if (enc_version >= 2)
random_key = json_object_get_string_member (object, "key");
if (enc_version >= 3)
salt = json_object_get_string_member (object, "salt");
if (json_object_has_member (object, "version"))
version = json_object_get_int_member (object, "version");
if (json_object_has_member (object, "new_merge"))
new_merge = json_object_get_int_member (object, "new_merge");
if (json_object_has_member (object, "conflict"))
conflict = json_object_get_int_member (object, "conflict");
if (json_object_has_member (object, "repaired"))
repaired = json_object_get_int_member (object, "repaired");
/* sanity check for incoming values. */
if (!repo_id || !is_uuid_valid(repo_id) ||
!root_id || !is_object_id_valid(root_id) ||
!creator || strlen(creator) != 40 ||
(parent_id && !is_object_id_valid(parent_id)) ||
(second_parent_id && !is_object_id_valid(second_parent_id)))
return commit;
// If pwd_hash is set, the magic field is no longer included in the commit of the newly created repo.
if (!magic)
magic = pwd_hash;
switch (enc_version) {
case 0:
break;
case 1:
if (!magic || strlen(magic) != 32)
return NULL;
break;
case 2:
if (!magic || strlen(magic) != 64)
return NULL;
if (!random_key || strlen(random_key) != 96)
return NULL;
break;
case 3:
if (!magic || strlen(magic) != 64)
return NULL;
if (!random_key || strlen(random_key) != 96)
return NULL;
if (!salt || strlen(salt) != 64)
return NULL;
break;
case 4:
if (!magic || strlen(magic) != 64)
return NULL;
if (!random_key || strlen(random_key) != 96)
return NULL;
if (!salt || strlen(salt) != 64)
return NULL;
break;
default:
seaf_warning ("Unknown encryption version %d.\n", enc_version);
return NULL;
}
char *creator_name_l = creator_name ? g_ascii_strdown (creator_name, -1) : NULL;
commit = seaf_commit_new (commit_id, repo_id, root_id,
creator_name_l, creator, desc, ctime);
g_free (creator_name_l);
commit->parent_id = parent_id ? g_strdup(parent_id) : NULL;
commit->second_parent_id = second_parent_id ? g_strdup(second_parent_id) : NULL;
commit->repo_name = g_strdup(repo_name);
commit->repo_desc = g_strdup(repo_desc);
if (encrypted && strcmp(encrypted, "true") == 0)
commit->encrypted = TRUE;
else
commit->encrypted = FALSE;
if (repo_category)
commit->repo_category = g_strdup(repo_category);
commit->device_name = g_strdup(device_name);
commit->client_version = g_strdup(client_version);
if (commit->encrypted) {
commit->enc_version = enc_version;
if (enc_version >= 1 && !pwd_hash)
commit->magic = g_strdup(magic);
if (enc_version >= 2)
commit->random_key = g_strdup (random_key);
if (enc_version >= 3)
commit->salt = g_strdup (salt);
if (pwd_hash) {
commit->pwd_hash = g_strdup (pwd_hash);
commit->pwd_hash_algo = g_strdup (pwd_hash_algo);
commit->pwd_hash_params = g_strdup (pwd_hash_params);
}
}
commit->version = version;
if (new_merge)
commit->new_merge = TRUE;
if (conflict)
commit->conflict = TRUE;
if (repaired)
commit->repaired = TRUE;
return commit;
}
static SeafCommit *
load_commit (SeafCommitManager *mgr,
const char *repo_id,
int version,
const char *commit_id)
{
char *data = NULL;
int len;
SeafCommit *commit = NULL;
json_t *object = NULL;
json_error_t jerror;
if (!commit_id || strlen(commit_id) != 40)
return NULL;
if (seaf_obj_store_read_obj (mgr->obj_store, repo_id, version,
commit_id, (void **)&data, &len) < 0)
return NULL;
object = json_loadb (data, len, 0, &jerror);
if (!object) {
/* Perhaps the commit object contains invalid UTF-8 character. */
if (data[len-1] == 0)
clean_utf8_data (data, len - 1);
else
clean_utf8_data (data, len);
object = json_loadb (data, len, 0, &jerror);
if (!object) {
seaf_warning ("Failed to load commit json object: %s.\n", jerror.text);
goto out;
}
}
commit = commit_from_json_object (commit_id, object);
if (commit)
commit->manager = mgr;
out:
if (object) json_decref (object);
g_free (data);
return commit;
}
static int
save_commit (SeafCommitManager *manager,
const char *repo_id,
int version,
SeafCommit *commit)
{
json_t *object = NULL;
char *data;
gsize len;
object = commit_to_json_object (commit);
data = json_dumps (object, 0);
len = strlen (data);
json_decref (object);
#ifdef SEAFILE_SERVER
if (seaf_obj_store_write_obj (manager->obj_store,
repo_id, version,
commit->commit_id,
data, (int)len, TRUE) < 0) {
g_free (data);
return -1;
}
#else
if (seaf_obj_store_write_obj (manager->obj_store,
repo_id, version,
commit->commit_id,
data, (int)len, FALSE) < 0) {
g_free (data);
return -1;
}
#endif
free (data);
return 0;
}
static void
delete_commit (SeafCommitManager *mgr,
const char *repo_id,
int version,
const char *id)
{
seaf_obj_store_delete_obj (mgr->obj_store, repo_id, version, id);
}
int
seaf_commit_manager_remove_store (SeafCommitManager *mgr,
const char *store_id)
{
return seaf_obj_store_remove_store (mgr->obj_store, store_id);
}
seadrive-fuse-3.0.13/src/commit-mgr.h 0000664 0000000 0000000 00000014513 14761776747 0017411 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#ifndef SEAF_COMMIT_MGR_H
#define SEAF_COMMIT_MGR_H
struct _SeafCommitManager;
typedef struct _SeafCommit SeafCommit;
#include
#include "db.h"
#include "obj-store.h"
struct _SeafCommit {
struct _SeafCommitManager *manager;
int ref;
char commit_id[41];
char repo_id[37];
char root_id[41]; /* the fs root */
char *desc;
char *creator_name;
char creator_id[41];
guint64 ctime; /* creation time */
char *parent_id;
char *second_parent_id;
char *repo_name;
char *repo_desc;
char *repo_category;
char *device_name;
char *client_version;
gboolean encrypted;
int enc_version;
char *magic;
char *random_key;
char *salt;
char *pwd_hash;
char *pwd_hash_algo;
char *pwd_hash_params;
int version;
gboolean new_merge;
gboolean conflict;
gboolean repaired;
};
/**
* @commit_id: if this is NULL, will create a new id.
* @ctime: if this is 0, will use current time.
*
* Any new commit should be added to commit manager before used.
*/
SeafCommit *
seaf_commit_new (const char *commit_id,
const char *repo_id,
const char *root_id,
const char *author_name,
const char *creator_id,
const char *desc,
guint64 ctime);
char *
seaf_commit_to_data (SeafCommit *commit, gsize *len);
SeafCommit *
seaf_commit_from_data (const char *id, char *data, gsize len);
void
seaf_commit_ref (SeafCommit *commit);
void
seaf_commit_unref (SeafCommit *commit);
/* Set stop to TRUE if you want to stop traversing a branch in the history graph.
Note, if currently there are multi branches, this function will be called again.
So, set stop to TRUE not always stop traversing the history graph.
*/
typedef gboolean (*CommitTraverseFunc) (SeafCommit *commit, void *data, gboolean *stop);
struct _SeafileSession;
typedef struct _SeafCommitManager SeafCommitManager;
typedef struct _SeafCommitManagerPriv SeafCommitManagerPriv;
struct _SeafCommitManager {
struct _SeafileSession *seaf;
sqlite3 *db;
struct SeafObjStore *obj_store;
SeafCommitManagerPriv *priv;
};
SeafCommitManager *
seaf_commit_manager_new (struct _SeafileSession *seaf);
int
seaf_commit_manager_init (SeafCommitManager *mgr);
/**
* Add a commit to commit manager and persist it to disk.
* Any new commit should be added to commit manager before used.
* This function increments ref count of the commit object.
* Not MT safe.
*/
int
seaf_commit_manager_add_commit (SeafCommitManager *mgr, SeafCommit *commit);
/**
* Delete a commit from commit manager and permanently remove it from disk.
* A commit object to be deleted should have ref cournt <= 1.
* Not MT safe.
*/
void
seaf_commit_manager_del_commit (SeafCommitManager *mgr,
const char *repo_id,
int version,
const char *id);
/**
* Find a commit object.
* This function increments ref count of returned object.
* Not MT safe.
*/
SeafCommit*
seaf_commit_manager_get_commit (SeafCommitManager *mgr,
const char *repo_id,
int version,
const char *id);
/**
* Get a commit object, with compatibility between version 0 and version 1.
* It will first try to get commit with version 1 layout; if fails, will
* try version 0 layout for compatibility.
* This is useful for loading a repo. In that case, we don't know the version
* of the repo before loading its head commit.
*/
SeafCommit *
seaf_commit_manager_get_commit_compatible (SeafCommitManager *mgr,
const char *repo_id,
const char *id);
/**
* Traverse the commits DAG start from head in topological order.
* The ordering is based on commit time.
* return FALSE if some commits is missing, TRUE otherwise.
*/
gboolean
seaf_commit_manager_traverse_commit_tree (SeafCommitManager *mgr,
const char *repo_id,
int version,
const char *head,
CommitTraverseFunc func,
void *data,
gboolean skip_errors);
/*
* The same as the above function, but stops traverse down if parent commit
* doesn't exists, instead of returning error.
*/
gboolean
seaf_commit_manager_traverse_commit_tree_truncated (SeafCommitManager *mgr,
const char *repo_id,
int version,
const char *head,
CommitTraverseFunc func,
void *data,
gboolean skip_errors);
/**
* Works the same as seaf_commit_manager_traverse_commit_tree, but stops
* traversing when a total number of _limit_ commits is reached. If
* limit <= 0, there is no limit
*/
gboolean
seaf_commit_manager_traverse_commit_tree_with_limit (SeafCommitManager *mgr,
const char *repo_id,
int version,
const char *head,
CommitTraverseFunc func,
int limit,
void *data,
gboolean skip_errors);
gboolean
seaf_commit_manager_commit_exists (SeafCommitManager *mgr,
const char *repo_id,
int version,
const char *id);
int
seaf_commit_manager_remove_store (SeafCommitManager *mgr,
const char *store_id);
#endif
seadrive-fuse-3.0.13/src/common.h 0000664 0000000 0000000 00000001275 14761776747 0016627 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#ifndef COMMON_H
#define COMMON_H
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include /* uint32_t */
#include /* size_t */
#include
#include
#include
#include
#include
#include
#define EMPTY_SHA1 "0000000000000000000000000000000000000000"
#define CURRENT_ENC_VERSION 2
#define DEFAULT_PROTO_VERSION 1
#define CURRENT_PROTO_VERSION 7
#define CURRENT_REPO_VERSION 1
#define SEAF_PATH_MAX 4096
#define MAX_GET_FINISHED_FILES 10
#endif
seadrive-fuse-3.0.13/src/curl-init.c 0000664 0000000 0000000 00000002443 14761776747 0017236 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#include "common.h"
#ifndef USE_GPL_CRYPTO
#include
#include
#include
#include "curl-init.h"
pthread_mutex_t* curl_locks = NULL;
void seafile_curl_locking_callback(int mode, int n, const char* file, int line)
{
if (mode & CRYPTO_LOCK) {
pthread_mutex_lock (&curl_locks[n]);
} else {
pthread_mutex_unlock (&curl_locks[n]);
}
}
void seafile_curl_init()
{
curl_global_init (CURL_GLOBAL_ALL);
int i;
curl_locks = malloc (sizeof(pthread_mutex_t) * CRYPTO_num_locks());
for (i = 0; i < CRYPTO_num_locks(); ++i) {
pthread_mutex_init (&curl_locks[i], NULL);
}
/* On Windows it's better to use the default id_function.
* As per http://linux.die.net/man/3/crypto_set_id_callback,
* the default id_functioin uses system's default thread
* identifying API.
*/
CRYPTO_set_id_callback (pthread_self);
CRYPTO_set_locking_callback (seafile_curl_locking_callback);
}
void seafile_curl_deinit()
{
int i;
CRYPTO_set_id_callback (0);
CRYPTO_set_locking_callback (0);
for (i = 0; i < CRYPTO_num_locks(); ++i) {
pthread_mutex_destroy (&curl_locks[i]);
}
free (curl_locks);
}
#endif
seadrive-fuse-3.0.13/src/curl-init.h 0000664 0000000 0000000 00000000306 14761776747 0017237 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#ifndef __CURL_INIT_H__
#define __CURL_INIT_H__
void seafile_curl_init(void);
void seafile_curl_deinit(void);
#endif
seadrive-fuse-3.0.13/src/db.c 0000664 0000000 0000000 00000012101 14761776747 0015705 0 ustar 00root root 0000000 0000000
#include
#include
#include "db.h"
int
sqlite_open_db (const char *db_path, sqlite3 **db)
{
int result;
const char *errmsg;
result = sqlite3_open (db_path, db);
if (result) {
errmsg = sqlite3_errmsg (*db);
g_warning ("Couldn't open database:'%s', %s\n",
db_path, errmsg ? errmsg : "no error given");
sqlite3_close (*db);
return -1;
}
return 0;
}
int sqlite_close_db (sqlite3 *db)
{
return sqlite3_close (db);
}
sqlite3_stmt *
sqlite_query_prepare (sqlite3 *db, const char *sql)
{
sqlite3_stmt *stmt;
int result;
result = sqlite3_prepare_v2 (db, sql, -1, &stmt, NULL);
if (result != SQLITE_OK) {
const gchar *str = sqlite3_errmsg (db);
g_warning ("Couldn't prepare query, error:%d->'%s'\n\t%s\n",
result, str ? str : "no error given", sql);
return NULL;
}
return stmt;
}
int
sqlite_query_exec (sqlite3 *db, const char *sql)
{
char *errmsg = NULL;
int result;
result = sqlite3_exec (db, sql, NULL, NULL, &errmsg);
if (result != SQLITE_OK) {
if (errmsg != NULL) {
g_warning ("SQL error: %d - %s\n:\t%s\n", result, errmsg, sql);
sqlite3_free (errmsg);
}
return -1;
}
return 0;
}
int
sqlite_begin_transaction (sqlite3 *db)
{
char *sql = "BEGIN TRANSACTION;";
return sqlite_query_exec (db, sql);
}
int
sqlite_end_transaction (sqlite3 *db)
{
char *sql = "END TRANSACTION;";
return sqlite_query_exec (db, sql);
}
int
sqlite_rollback_transaction (sqlite3 *db)
{
char *sql = "ROLLBACK TRANSACTION;";
return sqlite_query_exec (db, sql);
}
gboolean
sqlite_check_for_existence (sqlite3 *db, const char *sql)
{
sqlite3_stmt *stmt;
int result;
stmt = sqlite_query_prepare (db, sql);
if (!stmt)
return FALSE;
result = sqlite3_step (stmt);
if (result == SQLITE_ERROR) {
const gchar *str = sqlite3_errmsg (db);
g_warning ("Couldn't execute query, error: %d->'%s'\n",
result, str ? str : "no error given");
sqlite3_finalize (stmt);
return FALSE;
}
sqlite3_finalize (stmt);
if (result == SQLITE_ROW)
return TRUE;
return FALSE;
}
int
sqlite_foreach_selected_row (sqlite3 *db, const char *sql,
SqliteRowFunc callback, void *data)
{
sqlite3_stmt *stmt;
int result;
int n_rows = 0;
stmt = sqlite_query_prepare (db, sql);
if (!stmt) {
return -1;
}
while (1) {
result = sqlite3_step (stmt);
if (result != SQLITE_ROW)
break;
n_rows++;
if (!callback (stmt, data))
break;
}
if (result == SQLITE_ERROR) {
const gchar *s = sqlite3_errmsg (db);
g_warning ("Couldn't execute query, error: %d->'%s'\n",
result, s ? s : "no error given");
sqlite3_finalize (stmt);
return -1;
}
sqlite3_finalize (stmt);
return n_rows;
}
int sqlite_get_int (sqlite3 *db, const char *sql)
{
int ret = -1;
int result;
sqlite3_stmt *stmt;
if ( !(stmt = sqlite_query_prepare(db, sql)) )
return 0;
result = sqlite3_step (stmt);
if (result == SQLITE_ROW) {
ret = sqlite3_column_int (stmt, 0);
sqlite3_finalize (stmt);
return ret;
}
if (result == SQLITE_ERROR) {
const gchar *str = sqlite3_errmsg (db);
g_warning ("Couldn't execute query, error: %d->'%s'\n",
result, str ? str : "no error given");
sqlite3_finalize (stmt);
return -1;
}
sqlite3_finalize(stmt);
return ret;
}
gint64 sqlite_get_int64 (sqlite3 *db, const char *sql)
{
gint64 ret = -1;
int result;
sqlite3_stmt *stmt;
if ( !(stmt = sqlite_query_prepare(db, sql)) )
return 0;
result = sqlite3_step (stmt);
if (result == SQLITE_ROW) {
ret = sqlite3_column_int64 (stmt, 0);
sqlite3_finalize (stmt);
return ret;
}
if (result == SQLITE_ERROR) {
const gchar *str = sqlite3_errmsg (db);
g_warning ("Couldn't execute query, error: %d->'%s'\n",
result, str ? str : "no error given");
sqlite3_finalize (stmt);
return -1;
}
sqlite3_finalize(stmt);
return ret;
}
char *sqlite_get_string (sqlite3 *db, const char *sql)
{
const char *res = NULL;
int result;
sqlite3_stmt *stmt;
char *ret;
if ( !(stmt = sqlite_query_prepare(db, sql)) )
return NULL;
result = sqlite3_step (stmt);
if (result == SQLITE_ROW) {
res = (const char *)sqlite3_column_text (stmt, 0);
ret = g_strdup(res);
sqlite3_finalize (stmt);
return ret;
}
if (result == SQLITE_ERROR) {
const gchar *str = sqlite3_errmsg (db);
g_warning ("Couldn't execute query, error: %d->'%s'\n",
result, str ? str : "no error given");
sqlite3_finalize (stmt);
return NULL;
}
sqlite3_finalize(stmt);
return NULL;
}
seadrive-fuse-3.0.13/src/db.h 0000664 0000000 0000000 00000001645 14761776747 0015725 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#ifndef DB_UTILS_H
#define DB_UTILS_H
#include
int sqlite_open_db (const char *db_path, sqlite3 **db);
int sqlite_close_db (sqlite3 *db);
sqlite3_stmt *sqlite_query_prepare (sqlite3 *db, const char *sql);
int sqlite_query_exec (sqlite3 *db, const char *sql);
int sqlite_begin_transaction (sqlite3 *db);
int sqlite_end_transaction (sqlite3 *db);
int sqlite_rollback_transaction (sqlite3 *db);
gboolean sqlite_check_for_existence (sqlite3 *db, const char *sql);
typedef gboolean (*SqliteRowFunc) (sqlite3_stmt *stmt, void *data);
int
sqlite_foreach_selected_row (sqlite3 *db, const char *sql,
SqliteRowFunc callback, void *data);
int sqlite_get_int (sqlite3 *db, const char *sql);
gint64 sqlite_get_int64 (sqlite3 *db, const char *sql);
char *sqlite_get_string (sqlite3 *db, const char *sql);
#endif
seadrive-fuse-3.0.13/src/diff-simple.c 0000664 0000000 0000000 00000054706 14761776747 0017540 0 ustar 00root root 0000000 0000000 #include "common.h"
#include "diff-simple.h"
#include "utils.h"
#include "log.h"
DiffEntry *
diff_entry_new (char type, char status, unsigned char *sha1, const char *name)
{
DiffEntry *de = g_new0 (DiffEntry, 1);
de->type = type;
de->status = status;
memcpy (de->sha1, sha1, 20);
de->name = g_strdup(name);
return de;
}
DiffEntry *
diff_entry_new_from_dirent (char type, char status,
SeafDirent *dent, const char *basedir)
{
DiffEntry *de = g_new0 (DiffEntry, 1);
unsigned char sha1[20];
char *path;
hex_to_rawdata (dent->id, sha1, 20);
path = g_strconcat (basedir, dent->name, NULL);
de->type = type;
de->status = status;
memcpy (de->sha1, sha1, 20);
de->name = path;
if (type == DIFF_TYPE_COMMITS &&
(status == DIFF_STATUS_ADDED ||
status == DIFF_STATUS_MODIFIED ||
status == DIFF_STATUS_DIR_ADDED ||
status == DIFF_STATUS_DIR_DELETED)) {
de->mtime = dent->mtime;
de->mode = dent->mode;
de->modifier = g_strdup(dent->modifier);
de->size = dent->size;
}
return de;
}
void
diff_entry_free (DiffEntry *de)
{
g_free (de->name);
if (de->new_name)
g_free (de->new_name);
g_free (de->modifier);
g_free (de);
}
inline static gboolean
dirent_same (SeafDirent *denta, SeafDirent *dentb)
{
return (strcmp (dentb->id, denta->id) == 0 &&
denta->mode == dentb->mode &&
denta->mtime == dentb->mtime);
}
static int
diff_files (int n, SeafDirent *dents[], const char *basedir, DiffOptions *opt)
{
SeafDirent *files[3];
int i, n_files = 0;
memset (files, 0, sizeof(files[0])*n);
for (i = 0; i < n; ++i) {
if (dents[i] && S_ISREG(dents[i]->mode)) {
files[i] = dents[i];
++n_files;
}
}
if (n_files == 0)
return 0;
return opt->file_cb (n, basedir, files, opt->data);
}
static int
diff_trees_recursive (int n, SeafDir *trees[],
const char *basedir, DiffOptions *opt);
static int
diff_directories (int n, SeafDirent *dents[], const char *basedir, DiffOptions *opt)
{
SeafDirent *dirs[3];
int i, n_dirs = 0;
char *dirname = "";
int ret;
SeafDir *sub_dirs[3], *dir;
memset (dirs, 0, sizeof(dirs[0])*n);
for (i = 0; i < n; ++i) {
if (dents[i] && S_ISDIR(dents[i]->mode)) {
dirs[i] = dents[i];
++n_dirs;
}
}
if (n_dirs == 0)
return 0;
gboolean recurse = TRUE;
ret = opt->dir_cb (n, basedir, dirs, opt->data, &recurse);
if (ret < 0)
return ret;
if (!recurse)
return 0;
memset (sub_dirs, 0, sizeof(sub_dirs[0])*n);
for (i = 0; i < n; ++i) {
if (dents[i] != NULL && S_ISDIR(dents[i]->mode)) {
dir = seaf_fs_manager_get_seafdir (seaf->fs_mgr,
opt->store_id,
opt->version,
dents[i]->id);
if (!dir) {
seaf_warning ("Failed to find dir %s:%s.\n",
opt->store_id, dents[i]->id);
ret = -1;
goto free_sub_dirs;
}
sub_dirs[i] = dir;
dirname = dents[i]->name;
}
}
char *new_basedir = g_strconcat (basedir, dirname, "/", NULL);
ret = diff_trees_recursive (n, sub_dirs, new_basedir, opt);
g_free (new_basedir);
free_sub_dirs:
for (i = 0; i < n; ++i)
seaf_dir_free (sub_dirs[i]);
return ret;
}
static int
diff_trees_recursive (int n, SeafDir *trees[],
const char *basedir, DiffOptions *opt)
{
GList *ptrs[3];
SeafDirent *dents[3];
int i;
SeafDirent *dent;
char *first_name;
gboolean done;
int ret = 0;
for (i = 0; i < n; ++i) {
if (trees[i])
ptrs[i] = trees[i]->entries;
else
ptrs[i] = NULL;
}
while (1) {
first_name = NULL;
memset (dents, 0, sizeof(dents[0])*n);
done = TRUE;
/* Find the "largest" name, assuming dirents are sorted. */
for (i = 0; i < n; ++i) {
if (ptrs[i] != NULL) {
done = FALSE;
dent = ptrs[i]->data;
if (!first_name)
first_name = dent->name;
else if (strcmp(dent->name, first_name) > 0)
first_name = dent->name;
}
}
if (done)
break;
/*
* Setup dir entries for all names that equal to first_name
*/
for (i = 0; i < n; ++i) {
if (ptrs[i] != NULL) {
dent = ptrs[i]->data;
if (strcmp(first_name, dent->name) == 0) {
dents[i] = dent;
ptrs[i] = ptrs[i]->next;
}
}
}
if (n == 2 && dents[0] && dents[1] && dirent_same(dents[0], dents[1]))
continue;
if (n == 3 && dents[0] && dents[1] && dents[2] &&
dirent_same(dents[0], dents[1]) && dirent_same(dents[0], dents[2]))
continue;
/* Diff files of this level. */
ret = diff_files (n, dents, basedir, opt);
if (ret < 0)
return ret;
/* Recurse into sub level. */
ret = diff_directories (n, dents, basedir, opt);
if (ret < 0)
return ret;
}
return ret;
}
int
diff_trees (int n, const char *roots[], DiffOptions *opt)
{
SeafDir **trees, *root;
int i, ret;
g_return_val_if_fail (n == 2 || n == 3, -1);
trees = g_new0 (SeafDir *, n);
for (i = 0; i < n; ++i) {
root = seaf_fs_manager_get_seafdir (seaf->fs_mgr,
opt->store_id,
opt->version,
roots[i]);
if (!root) {
seaf_warning ("Failed to find dir %s:%s.\n", opt->store_id, roots[i]);
g_free (trees);
return -1;
}
trees[i] = root;
}
ret = diff_trees_recursive (n, trees, "", opt);
for (i = 0; i < n; ++i)
seaf_dir_free (trees[i]);
g_free (trees);
return ret;
}
typedef struct DiffData {
GList **results;
gboolean fold_dir_diff;
} DiffData;
static int
twoway_diff_files (int n, const char *basedir, SeafDirent *files[], void *vdata)
{
DiffData *data = vdata;
GList **results = data->results;
DiffEntry *de;
SeafDirent *tree1 = files[0];
SeafDirent *tree2 = files[1];
unsigned char sha1[20];
if (!tree1) {
de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_ADDED,
tree2, basedir);
*results = g_list_prepend (*results, de);
return 0;
}
if (!tree2) {
de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_DELETED,
tree1, basedir);
*results = g_list_prepend (*results, de);
return 0;
}
if (!dirent_same (tree1, tree2)) {
de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_MODIFIED,
tree2, basedir);
hex_to_rawdata (tree1->id, sha1, 20);
memcpy (de->old_sha1, sha1, 20);
*results = g_list_prepend (*results, de);
}
return 0;
}
static int
twoway_diff_dirs (int n, const char *basedir, SeafDirent *dirs[], void *vdata,
gboolean *recurse)
{
DiffData *data = vdata;
GList **results = data->results;
DiffEntry *de;
SeafDirent *tree1 = dirs[0];
SeafDirent *tree2 = dirs[1];
if (!tree1) {
if (strcmp (tree2->id, EMPTY_SHA1) == 0 || data->fold_dir_diff) {
de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_DIR_ADDED,
tree2, basedir);
*results = g_list_prepend (*results, de);
*recurse = FALSE;
} else
*recurse = TRUE;
return 0;
}
if (!tree2) {
de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS,
DIFF_STATUS_DIR_DELETED,
tree1, basedir);
*results = g_list_prepend (*results, de);
if (data->fold_dir_diff) {
*recurse = FALSE;
} else
*recurse = TRUE;
return 0;
}
return 0;
}
int
diff_commits (SeafCommit *commit1, SeafCommit *commit2, GList **results,
gboolean fold_dir_diff)
{
SeafRepo *repo = NULL;
DiffOptions opt;
const char *roots[2];
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, commit1->repo_id);
if (!repo) {
seaf_warning ("Failed to get repo %s.\n", commit1->repo_id);
return -1;
}
DiffData data;
memset (&data, 0, sizeof(data));
data.results = results;
data.fold_dir_diff = fold_dir_diff;
memset (&opt, 0, sizeof(opt));
memcpy (opt.store_id, repo->id, 36);
opt.version = repo->version;
opt.file_cb = twoway_diff_files;
opt.dir_cb = twoway_diff_dirs;
opt.data = &data;
roots[0] = commit1->root_id;
roots[1] = commit2->root_id;
diff_trees (2, roots, &opt);
diff_resolve_renames (results);
seaf_repo_unref (repo);
return 0;
}
int
diff_commit_roots (const char *store_id, int version,
const char *root1, const char *root2, GList **results,
gboolean fold_dir_diff)
{
DiffOptions opt;
const char *roots[2];
DiffData data;
memset (&data, 0, sizeof(data));
data.results = results;
data.fold_dir_diff = fold_dir_diff;
memset (&opt, 0, sizeof(opt));
memcpy (opt.store_id, store_id, 36);
opt.version = version;
opt.file_cb = twoway_diff_files;
opt.dir_cb = twoway_diff_dirs;
opt.data = &data;
roots[0] = root1;
roots[1] = root2;
diff_trees (2, roots, &opt);
diff_resolve_renames (results);
return 0;
}
static int
threeway_diff_files (int n, const char *basedir, SeafDirent *files[], void *vdata)
{
DiffData *data = vdata;
SeafDirent *m = files[0];
SeafDirent *p1 = files[1];
SeafDirent *p2 = files[2];
GList **results = data->results;
DiffEntry *de;
/* diff m with both p1 and p2. */
if (m && p1 && p2) {
if (!dirent_same(m, p1) && !dirent_same (m, p2)) {
de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_MODIFIED,
m, basedir);
*results = g_list_prepend (*results, de);
}
} else if (!m && p1 && p2) {
de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_DELETED,
p1, basedir);
*results = g_list_prepend (*results, de);
} else if (m && !p1 && p2) {
if (!dirent_same (m, p2)) {
de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_MODIFIED,
m, basedir);
*results = g_list_prepend (*results, de);
}
} else if (m && p1 && !p2) {
if (!dirent_same (m, p1)) {
de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_MODIFIED,
m, basedir);
*results = g_list_prepend (*results, de);
}
} else if (m && !p1 && !p2) {
de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_ADDED,
m, basedir);
*results = g_list_prepend (*results, de);
}
/* Nothing to do for:
* 1. !m && p1 && !p2;
* 2. !m && !p1 && p2;
* 3. !m && !p1 && !p2 (should not happen)
*/
return 0;
}
static int
threeway_diff_dirs (int n, const char *basedir, SeafDirent *dirs[], void *vdata,
gboolean *recurse)
{
*recurse = TRUE;
return 0;
}
int
diff_merge (SeafCommit *merge, GList **results, gboolean fold_dir_diff)
{
SeafRepo *repo = NULL;
DiffOptions opt;
const char *roots[3];
SeafCommit *parent1, *parent2;
g_return_val_if_fail (*results == NULL, -1);
g_return_val_if_fail (merge->parent_id != NULL &&
merge->second_parent_id != NULL,
-1);
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, merge->repo_id);
if (!repo) {
seaf_warning ("Failed to get repo %s.\n", merge->repo_id);
return -1;
}
parent1 = seaf_commit_manager_get_commit (seaf->commit_mgr,
repo->id,
repo->version,
merge->parent_id);
if (!parent1) {
seaf_warning ("failed to find commit %s:%s.\n", repo->id, merge->parent_id);
seaf_repo_unref (repo);
return -1;
}
parent2 = seaf_commit_manager_get_commit (seaf->commit_mgr,
repo->id,
repo->version,
merge->second_parent_id);
if (!parent2) {
seaf_warning ("failed to find commit %s:%s.\n",
repo->id, merge->second_parent_id);
seaf_repo_unref (repo);
seaf_commit_unref (parent1);
return -1;
}
DiffData data;
memset (&data, 0, sizeof(data));
data.results = results;
data.fold_dir_diff = fold_dir_diff;
memset (&opt, 0, sizeof(opt));
memcpy (opt.store_id, repo->id, 36);
opt.version = repo->version;
opt.file_cb = threeway_diff_files;
opt.dir_cb = threeway_diff_dirs;
opt.data = &data;
roots[0] = merge->root_id;
roots[1] = parent1->root_id;
roots[2] = parent2->root_id;
int ret = diff_trees (3, roots, &opt);
diff_resolve_renames (results);
seaf_repo_unref (repo);
seaf_commit_unref (parent1);
seaf_commit_unref (parent2);
return ret;
}
int
diff_merge_roots (const char *store_id, int version,
const char *merged_root, const char *p1_root, const char *p2_root,
GList **results, gboolean fold_dir_diff)
{
DiffOptions opt;
const char *roots[3];
g_return_val_if_fail (*results == NULL, -1);
DiffData data;
memset (&data, 0, sizeof(data));
data.results = results;
data.fold_dir_diff = fold_dir_diff;
memset (&opt, 0, sizeof(opt));
memcpy (opt.store_id, store_id, 36);
opt.version = version;
opt.file_cb = threeway_diff_files;
opt.dir_cb = threeway_diff_dirs;
opt.data = &data;
roots[0] = merged_root;
roots[1] = p1_root;
roots[2] = p2_root;
diff_trees (3, roots, &opt);
diff_resolve_renames (results);
return 0;
}
/* This function only resolve "strict" rename, i.e. two files must be
* exactly the same.
* Don't detect rename of empty files and empty dirs.
*/
void
diff_resolve_renames (GList **diff_entries)
{
GHashTable *deleted;
GList *p;
GList *added = NULL;
DiffEntry *de;
unsigned char empty_sha1[20];
memset (empty_sha1, 0, 20);
/* Hash and equal functions for raw sha1. */
deleted = g_hash_table_new (ccnet_sha1_hash, ccnet_sha1_equal);
/* Collect all "deleted" entries. */
for (p = *diff_entries; p != NULL; p = p->next) {
de = p->data;
if ((de->status == DIFF_STATUS_DELETED ||
de->status == DIFF_STATUS_DIR_DELETED) &&
memcmp (de->sha1, empty_sha1, 20) != 0)
g_hash_table_insert (deleted, de->sha1, p);
}
/* Collect all "added" entries into a separate list. */
for (p = *diff_entries; p != NULL; p = p->next) {
de = p->data;
if ((de->status == DIFF_STATUS_ADDED ||
de->status == DIFF_STATUS_DIR_ADDED) &&
memcmp (de->sha1, empty_sha1, 20) != 0)
added = g_list_prepend (added, p);
}
/* For each "added" entry, if we find a "deleted" entry with
* the same content, we find a rename pair.
*/
p = added;
while (p != NULL) {
GList *p_add, *p_del;
DiffEntry *de_add, *de_del, *de_rename;
int rename_status;
p_add = p->data;
de_add = p_add->data;
p_del = g_hash_table_lookup (deleted, de_add->sha1);
if (p_del) {
de_del = p_del->data;
if (de_add->status == DIFF_STATUS_DIR_ADDED)
rename_status = DIFF_STATUS_DIR_RENAMED;
else
rename_status = DIFF_STATUS_RENAMED;
de_rename = diff_entry_new (de_del->type, rename_status,
de_del->sha1, de_del->name);
de_rename->new_name = g_strdup(de_add->name);
*diff_entries = g_list_delete_link (*diff_entries, p_add);
*diff_entries = g_list_delete_link (*diff_entries, p_del);
*diff_entries = g_list_prepend (*diff_entries, de_rename);
g_hash_table_remove (deleted, de_add->sha1);
diff_entry_free (de_add);
diff_entry_free (de_del);
}
p = g_list_delete_link (p, p);
}
g_hash_table_destroy (deleted);
}
static gboolean
is_redundant_empty_dir (DiffEntry *de_dir, DiffEntry *de_file)
{
int dir_len;
if (de_dir->status == DIFF_STATUS_DIR_ADDED &&
de_file->status == DIFF_STATUS_DELETED)
{
dir_len = strlen (de_dir->name);
if (strlen (de_file->name) > dir_len &&
strncmp (de_dir->name, de_file->name, dir_len) == 0)
return TRUE;
}
if (de_dir->status == DIFF_STATUS_DIR_DELETED &&
de_file->status == DIFF_STATUS_ADDED)
{
dir_len = strlen (de_dir->name);
if (strlen (de_file->name) > dir_len &&
strncmp (de_dir->name, de_file->name, dir_len) == 0)
return TRUE;
}
return FALSE;
}
/*
* An empty dir entry may be added by deleting all the files under it.
* Similarly, an empty dir entry may be deleted by adding some file in it.
* In both cases, we don't want to include the empty dir entry in the
* diff results.
*/
void
diff_resolve_empty_dirs (GList **diff_entries)
{
GList *empty_dirs = NULL;
GList *p, *dir, *file;
DiffEntry *de, *de_dir, *de_file;
for (p = *diff_entries; p != NULL; p = p->next) {
de = p->data;
if (de->status == DIFF_STATUS_DIR_ADDED ||
de->status == DIFF_STATUS_DIR_DELETED)
empty_dirs = g_list_prepend (empty_dirs, p);
}
for (dir = empty_dirs; dir != NULL; dir = dir->next) {
de_dir = ((GList *)dir->data)->data;
for (file = *diff_entries; file != NULL; file = file->next) {
de_file = file->data;
if (is_redundant_empty_dir (de_dir, de_file)) {
*diff_entries = g_list_delete_link (*diff_entries, dir->data);
break;
}
}
}
g_list_free (empty_dirs);
}
inline static char *
get_basename (char *path)
{
char *slash;
slash = strrchr (path, '/');
if (!slash)
return path;
return (slash + 1);
}
char *
diff_results_to_description (GList *results)
{
GList *p;
DiffEntry *de;
char *added_file = NULL, *mod_file = NULL, *removed_file = NULL;
char *renamed_file = NULL, *renamed_dir = NULL;
char *new_dir = NULL, *removed_dir = NULL;
int n_added = 0, n_mod = 0, n_removed = 0, n_renamed = 0;
int n_new_dir = 0, n_removed_dir = 0, n_renamed_dir = 0;
GString *desc;
if (results == NULL)
return NULL;
for (p = results; p != NULL; p = p->next) {
de = p->data;
switch (de->status) {
case DIFF_STATUS_ADDED:
if (n_added == 0)
added_file = get_basename(de->name);
n_added++;
break;
case DIFF_STATUS_DELETED:
if (n_removed == 0)
removed_file = get_basename(de->name);
n_removed++;
break;
case DIFF_STATUS_RENAMED:
if (n_renamed == 0)
renamed_file = get_basename(de->name);
n_renamed++;
break;
case DIFF_STATUS_DIR_RENAMED:
if (n_renamed_dir == 0)
renamed_dir = get_basename(de->name);
n_renamed_dir++;
break;
case DIFF_STATUS_MODIFIED:
if (n_mod == 0)
mod_file = get_basename(de->name);
n_mod++;
break;
case DIFF_STATUS_DIR_ADDED:
if (n_new_dir == 0)
new_dir = get_basename(de->name);
n_new_dir++;
break;
case DIFF_STATUS_DIR_DELETED:
if (n_removed_dir == 0)
removed_dir = get_basename(de->name);
n_removed_dir++;
break;
}
}
desc = g_string_new ("");
if (n_added == 1)
g_string_append_printf (desc, "Added \"%s\".\n", added_file);
else if (n_added > 1)
g_string_append_printf (desc, "Added \"%s\" and %d more files.\n",
added_file, n_added - 1);
if (n_mod == 1)
g_string_append_printf (desc, "Modified \"%s\".\n", mod_file);
else if (n_mod > 1)
g_string_append_printf (desc, "Modified \"%s\" and %d more files.\n",
mod_file, n_mod - 1);
if (n_removed == 1)
g_string_append_printf (desc, "Deleted \"%s\".\n", removed_file);
else if (n_removed > 1)
g_string_append_printf (desc, "Deleted \"%s\" and %d more files.\n",
removed_file, n_removed - 1);
if (n_renamed == 1)
g_string_append_printf (desc, "Renamed \"%s\".\n", renamed_file);
else if (n_renamed > 1)
g_string_append_printf (desc, "Renamed \"%s\" and %d more files.\n",
renamed_file, n_renamed - 1);
if (n_renamed_dir == 1)
g_string_append_printf (desc, "Renamed directory \"%s\".\n", renamed_dir);
else if (n_renamed_dir > 1)
g_string_append_printf (desc, "Renamed \"%s\" and %d more directories.\n",
renamed_dir, n_renamed_dir - 1);
if (n_new_dir == 1)
g_string_append_printf (desc, "Added directory \"%s\".\n", new_dir);
else if (n_new_dir > 1)
g_string_append_printf (desc, "Added \"%s\" and %d more directories.\n",
new_dir, n_new_dir - 1);
if (n_removed_dir == 1)
g_string_append_printf (desc, "Removed directory \"%s\".\n", removed_dir);
else if (n_removed_dir > 1)
g_string_append_printf (desc, "Removed \"%s\" and %d more directories.\n",
removed_dir, n_removed_dir - 1);
return g_string_free (desc, FALSE);
}
seadrive-fuse-3.0.13/src/diff-simple.h 0000664 0000000 0000000 00000005530 14761776747 0017534 0 ustar 00root root 0000000 0000000 #ifndef DIFF_SIMPLE_H
#define DIFF_SIMPLE_H
#include
#include "seafile-session.h"
#define DIFF_TYPE_WORKTREE 'W' /* diff from index to worktree */
#define DIFF_TYPE_INDEX 'I' /* diff from commit to index */
#define DIFF_TYPE_COMMITS 'C' /* diff between two commits*/
#define DIFF_STATUS_ADDED 'A'
#define DIFF_STATUS_DELETED 'D'
#define DIFF_STATUS_MODIFIED 'M'
#define DIFF_STATUS_RENAMED 'R'
#define DIFF_STATUS_UNMERGED 'U'
#define DIFF_STATUS_DIR_ADDED 'B'
#define DIFF_STATUS_DIR_DELETED 'C'
#define DIFF_STATUS_DIR_RENAMED 'E'
typedef struct DiffEntry {
char type;
char status;
unsigned char sha1[20]; /* used for resolve rename */
unsigned char old_sha1[20]; /* set when status is MODIFIED, only for two way diff */
char *name;
char *new_name; /* only used in rename. */
/* Fields only used for ADDED, DIR_ADDED, MODIFIED types,
* used in check out files/dirs.*/
gint64 mtime;
unsigned int mode;
char *modifier;
gint64 size;
} DiffEntry;
DiffEntry *
diff_entry_new (char type, char status, unsigned char *sha1, const char *name);
void
diff_entry_free (DiffEntry *de);
/*
* @fold_dir_diff: if TRUE, only the top level directory will be included
* in the diff result if a directory with files is added or removed.
* Otherwise all the files in the direcotory will be recursively
* included in the diff result.
*/
int
diff_commits (SeafCommit *commit1, SeafCommit *commit2, GList **results,
gboolean fold_dir_diff);
int
diff_commit_roots (const char *store_id, int version,
const char *root1, const char *root2, GList **results,
gboolean fold_dir_diff);
int
diff_merge (SeafCommit *merge, GList **results, gboolean fold_dir_diff);
int
diff_merge_roots (const char *store_id, int version,
const char *merged_root, const char *p1_root, const char *p2_root,
GList **results, gboolean fold_dir_diff);
void
diff_resolve_renames (GList **diff_entries);
void
diff_resolve_empty_dirs (GList **diff_entries);
char *
diff_results_to_description (GList *results);
typedef int (*DiffFileCB) (int n,
const char *basedir,
SeafDirent *files[],
void *data);
typedef int (*DiffDirCB) (int n,
const char *basedir,
SeafDirent *dirs[],
void *data,
gboolean *recurse);
typedef struct DiffOptions {
char store_id[37];
int version;
DiffFileCB file_cb;
DiffDirCB dir_cb;
void *data;
} DiffOptions;
int
diff_trees (int n, const char *roots[], DiffOptions *opt);
#endif
seadrive-fuse-3.0.13/src/file-cache-mgr.c 0000664 0000000 0000000 00000261011 14761776747 0020071 0 ustar 00root root 0000000 0000000 #include "common.h"
#include
#include
#include
#include
#include "seafile-session.h"
#include "seafile-config.h"
#include "seafile-error.h"
#define DEBUG_FLAG SEAFILE_DEBUG_SYNC
#include "log.h"
#include "repo-tree.h"
#include "utils.h"
#include "timer.h"
#define MAX_THREADS 3
#define DEFAULT_CLEAN_CACHE_INTERVAL 600 // 10 minutes
#define DEFAULT_CACHE_SIZE_LIMIT (10000000000LL) // 10GB
/* true if the cached content is already on the server, otherwise false.
* This attr is used in cache cleaning.
*/
#define SEAFILE_UPLOADED_ATTR "user.uploaded"
/* These 3 attrs can be seen outside. */
#define SEAFILE_MTIME_ATTR "user.seafile-mtime"
#define SEAFILE_SIZE_ATTR "user.seafile-size"
#define SEAFILE_FILE_ID_ATTR "user.file-id"
typedef struct CachedFile {
// Identifier(repo_id/file_path) for CachedFile in memory
char *file_key;
// Record CachedFile open number
gint n_open;
// Repo unique name used when get download progress
char *repo_uname;
// Caculate download progress
gint64 downloaded;
gint64 total_download;
gboolean force_canceled;
gint64 last_cancel_time;
} CachedFile;
typedef struct FileCacheMgrPriv {
// Parent dir(seafile_dir/file_cache) to store cached files
char *base_path;
// repo_id/file_path <-> CachedFile
GHashTable *cached_files;
pthread_mutex_t cache_lock;
GThreadPool *tpool;
// repo_id/file_path <-> CachedFileHandle
GHashTable *cache_tasks;
pthread_mutex_t task_lock;
/* Cache block size list for files. */
GHashTable *block_map_cache;
pthread_rwlock_t block_map_lock;
SeafTimer *clean_timer;
int clean_cache_interval;
gint64 cache_size_limit;
GQueue *downloaded_files;
pthread_mutex_t downloaded_files_lock;
} FileCacheMgrPriv;
typedef struct FileDownloadedInfo {
char *file_path;
char *server;
char *user;
} FileDownloadedInfo;
static void
file_downloaded_info_free (FileDownloadedInfo *info)
{
if (!info)
return;
g_free (info->file_path);
g_free (info->server);
g_free (info->user);
g_free (info);
return;
}
static char *
cached_file_ondisk_path (CachedFile *file)
{
FileCacheMgrPriv *priv = seaf->file_cache_mgr->priv;
char **tokens = g_strsplit (file->file_key, "/", 2);
char *repo_id = tokens[0];
char *path = tokens[1];
char *ondisk_path = g_build_filename (priv->base_path, repo_id, path, NULL);
g_strfreev (tokens);
return ondisk_path;
}
static void
free_cached_file (CachedFile *file)
{
if (!file)
return;
g_free (file->file_key);
g_free (file->repo_uname);
g_free (file);
}
static void
cached_file_ref (CachedFile *file)
{
if (!file)
return;
g_atomic_int_inc (&file->n_open);
}
static void
cached_file_unref (CachedFile *file)
{
if (!file)
return;
if (g_atomic_int_dec_and_test (&file->n_open))
free_cached_file (file);
}
static CachedFileHandle *
cached_file_handle_new ()
{
CachedFileHandle *handle = g_new0 (CachedFileHandle, 1);
pthread_mutex_init (&handle->lock, NULL);
return handle;
}
static void
free_cached_file_handle (CachedFileHandle *file_handle)
{
if (!file_handle)
return;
close (file_handle->fd);
pthread_mutex_destroy (&file_handle->lock);
cached_file_unref (file_handle->cached_file);
if (file_handle->crypt)
g_free (file_handle->crypt);
g_free (file_handle->server);
g_free (file_handle->user);
g_free (file_handle);
}
gboolean
cached_file_handle_is_readonly (CachedFileHandle *file_handle)
{
return file_handle->is_readonly;
}
static void
fetch_file_worker (gpointer data, gpointer user_data);
static void *
clean_cache_worker (void *data);
static void *
remove_deleted_cache_worker (void *data);
static void *
check_download_file_time_worker (void *data);
FileCacheMgr *
file_cache_mgr_new (const char *parent_dir)
{
GError *error = NULL;
FileCacheMgr *mgr = g_new0 (FileCacheMgr, 1);
FileCacheMgrPriv *priv = g_new0 (FileCacheMgrPriv, 1);
priv->tpool = g_thread_pool_new (fetch_file_worker, priv, MAX_THREADS, FALSE, &error);
if (!priv->tpool) {
seaf_warning ("Failed to create thread pool for cache file: %s.\n",
error == NULL ? "" : error->message);
g_free (priv);
g_free (mgr);
return NULL;
}
priv->base_path = g_build_filename (parent_dir, "file-cache", NULL);
if (g_mkdir_with_parents (priv->base_path, 0777) < 0) {
seaf_warning ("Failed to create file_cache dir: %s.\n", strerror (errno));
g_free (priv->base_path);
g_thread_pool_free (priv->tpool, TRUE, FALSE);
g_free (priv);
g_free (mgr);
return NULL;
}
priv->cached_files = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
(GDestroyNotify)cached_file_unref);
pthread_mutex_init (&priv->cache_lock, NULL);
priv->cache_tasks = g_hash_table_new_full (g_str_hash, g_str_equal,
(GDestroyNotify)g_free,
NULL);
pthread_mutex_init (&priv->task_lock, NULL);
priv->block_map_cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
pthread_rwlock_init (&priv->block_map_lock, NULL);
priv->downloaded_files = g_queue_new ();
pthread_mutex_init (&priv->downloaded_files_lock, NULL);
mgr->priv = priv;
return mgr;
}
/* static void */
/* load_downloaded_file_list (FileCacheMgr *mgr); */
void
file_cache_mgr_init (FileCacheMgr *mgr)
{
int clean_cache_interval;
gint64 cache_size_limit;
gboolean exists;
clean_cache_interval = seafile_session_config_get_int (seaf,
KEY_CLEAN_CACHE_INTERVAL,
&exists);
if (!exists) {
clean_cache_interval = DEFAULT_CLEAN_CACHE_INTERVAL;
}
cache_size_limit = seafile_session_config_get_int64 (seaf,
KEY_CACHE_SIZE_LIMIT,
&exists);
if (!exists) {
cache_size_limit = DEFAULT_CACHE_SIZE_LIMIT;
}
mgr->priv->clean_cache_interval = clean_cache_interval;
mgr->priv->cache_size_limit = cache_size_limit;
/* load_downloaded_file_list (mgr); */
}
void
file_cache_mgr_start (FileCacheMgr *mgr)
{
pthread_t tid;
int rc;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
rc = pthread_create (&tid, &attr, clean_cache_worker, mgr);
if (rc != 0) {
seaf_warning ("Failed to create clean cache worker thread.\n");
}
rc = pthread_create (&tid, &attr, remove_deleted_cache_worker, mgr);
if (rc != 0) {
seaf_warning ("Failed to create remove deleted cache worker thread.\n");
}
rc = pthread_create (&tid, &attr, check_download_file_time_worker, mgr->priv);
if (rc != 0) {
seaf_warning ("Failed to check download file time worker thread.\n");
}
}
static void
cancel_fetch_task_by_file (FileCacheMgr *mgr, const char *file_key)
{
CachedFileHandle *handle;
pthread_mutex_lock (&mgr->priv->task_lock);
handle = g_hash_table_lookup (mgr->priv->cache_tasks, file_key);
if (handle)
handle->fetch_canceled = TRUE;
pthread_mutex_unlock (&mgr->priv->task_lock);
}
/* Cached file handling. */
static void
send_file_download_notification (const char *type, const char *repo_id, const char *path)
{
json_t *msg;
char *repo_uname, *fullpath;
msg = json_object ();
json_object_set_new (msg, "type", json_string(type));
repo_uname = seaf_repo_manager_get_repo_display_name (seaf->repo_mgr, repo_id);
fullpath = g_strconcat (repo_uname, "/", path, NULL);
json_object_set_new (msg, "path", json_string(fullpath));
mq_mgr_push_msg (seaf->mq_mgr, SEADRIVE_EVENT_CHAN, msg);
g_free (repo_uname);
g_free (fullpath);
}
static size_t
fill_block (void *contents, size_t size, size_t nmemb, void *userp)
{
CachedFileHandle *file_handle = userp;
size_t realsize = size *nmemb;
char *buffer = contents;
int ret = 0;
char *dec_out = NULL;
int dec_out_len = -1;
if (file_handle->crypt) {
ret = seafile_decrypt (&dec_out, &dec_out_len, buffer, realsize, file_handle->crypt);
if (ret != 0){
seaf_warning ("Decrypt block failed.\n");
return -1;
}
ret = writen (file_handle->fd, dec_out, dec_out_len);
} else {
ret = writen (file_handle->fd, buffer, realsize);
}
if (ret < 0) {
seaf_warning ("Failed to write cache file %s: %s.\n",
file_handle->cached_file->file_key, strerror (errno));
} else {
file_handle->cached_file->downloaded += ret;
}
if (!file_handle->crypt)
g_atomic_int_add (&(seaf->sync_mgr->recv_bytes), realsize);
g_free (dec_out);
return ret;
}
static size_t
get_encrypted_block_cb (void *contents, size_t size, size_t nmemb, void *userp)
{
size_t realsize = size *nmemb;
CachedFileHandle *file_handle = userp;
if (file_handle->cached_file->force_canceled) {
seaf_message ("Cancel fetching %s\n", file_handle->cached_file->file_key);
return 0;
}
/* If the task is marked as canceled and the fetch task is the only
* remaining referer to the cached file, it should be stopped.
*/
if (file_handle->fetch_canceled && file_handle->cached_file->n_open <= 2) {
seaf_debug ("Cancel fetching %s after close\n", file_handle->cached_file->file_key);
return 0;
}
file_handle->blk_buffer.content = g_realloc (file_handle->blk_buffer.content, file_handle->blk_buffer.size + realsize);
if (!file_handle->blk_buffer.content) {
seaf_warning ("Not enough memory.\n");
return 0;
}
memcpy (file_handle->blk_buffer.content + file_handle->blk_buffer.size, contents, realsize);
file_handle->blk_buffer.size += realsize;
g_atomic_int_add (&(seaf->sync_mgr->recv_bytes), realsize);
return realsize;
}
static size_t
get_block_cb (void *contents, size_t size, size_t nmemb, void *userp)
{
size_t realsize = size *nmemb;
CachedFileHandle *file_handle = userp;
if (file_handle->cached_file->force_canceled) {
seaf_message ("Cancel fetching %s\n", file_handle->cached_file->file_key);
return 0;
}
/* If the task is marked as canceled and the fetch task is the only
* remaining referer to the cached file, it should be stopped.
*/
if (file_handle->fetch_canceled && file_handle->cached_file->n_open <= 2) {
seaf_debug ("Cancel fetching %s after close\n", file_handle->cached_file->file_key);
return 0;
}
g_atomic_int_add (&(seaf->sync_mgr->recv_bytes), realsize);
fill_block (contents, size, nmemb, userp);
return realsize;
}
static int
mark_file_cached (const char *ondisk_path, RepoTreeStat *st)
{
if (seaf_set_file_time (ondisk_path, st->mtime) < 0) {
seaf_warning ("Failed to set mtime for %s.\n", ondisk_path);
return -1;
}
char attr[64];
int len;
if (seaf_setxattr (ondisk_path, SEAFILE_FILE_ID_ATTR, st->id, 41) < 0) {
seaf_warning ("Failed to set file-id xattr for %s: %s.\n",
ondisk_path, strerror(errno));
return -1;
}
len = snprintf (attr, sizeof(attr), "%"G_GINT64_FORMAT, st->size);
if (seaf_setxattr (ondisk_path, SEAFILE_SIZE_ATTR, attr, len+1) < 0) {
seaf_warning ("Failed to set seafile-size xattr for %s: %s.\n",
ondisk_path, strerror(errno));
return -1;
}
len = snprintf (attr, sizeof(attr), "%"G_GINT64_FORMAT, st->mtime);
if (seaf_setxattr (ondisk_path, SEAFILE_MTIME_ATTR, attr, len+1) < 0) {
seaf_warning ("Failed to set seafile-mtime xattr for %s: %s.\n",
ondisk_path, strerror(errno));
return -1;
}
return 0;
}
static int
get_file_from_server (HttpServerInfo *server_info, SeafRepo *repo,
const char *path,
gint64 block_offset,
CachedFileHandle *handle)
{
int http_status = 200;
if (http_tx_manager_get_file (seaf->http_tx_mgr, server_info->host,
server_info->use_fileserver_port, repo->token,
repo->id,
path, block_offset,
get_block_cb, handle,
&http_status) < 0) {
if (!handle->fetch_canceled && !handle->cached_file->force_canceled)
seaf_message ("Failed to get file %s from server\n", path);
if (handle->notified_download_start)
send_file_download_notification ("file-download.stop", repo->id, path);
return -1;
}
return 0;
}
static void
calculate_block_offset (Seafile *file, gint64 *block_map, CachedFileHandle *handle,
int *block_offset, gint64 *file_offset)
{
SeafStat st;
gint64 size;
int i;
gint64 offset, prev_offset;
if (seaf_fstat (handle->fd, &st) < 0) {
seaf_warning ("Failed to stat cached file %s: %s\n",
handle->cached_file->file_key, strerror(errno));
return;
}
size = (gint64)st.st_size;
offset = 0;
prev_offset = 0;
for (i = 0; i < file->n_blocks; ++i) {
prev_offset = offset;
offset += block_map[i];
if (offset > size)
break;
}
*block_offset = i;
*file_offset = prev_offset;
}
#define CACHE_BLOCK_MAP_THRESHOLD 3000000 /* 3MB */
static void
fetch_file_worker (gpointer data, gpointer user_data)
{
CachedFileHandle *file_handle = data;
FileCacheMgrPriv *priv = user_data;
SeafRepo *repo = NULL;
RepoTreeStat st;
Seafile *file = NULL;
HttpServerInfo *server_info = NULL;
char *file_key = NULL;
char **key_comps = NULL;
char *repo_id;
char *file_path;
char *ondisk_path = NULL;
int http_status = 200;
gint64 *block_map = NULL;
int n_blocks = 0;
int block_offset = 0;
gint64 file_offset = 0;
gboolean have_invisible = FALSE;
file_key = g_strdup (file_handle->cached_file->file_key);
key_comps = g_strsplit (file_handle->cached_file->file_key, "/", 2);
repo_id = key_comps[0];
file_path = key_comps[1];
ondisk_path = g_build_filename (priv->base_path, repo_id, file_path, NULL);
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo) {
seaf_warning ("Failed to get repo %.8s.\n", repo_id);
goto out;
}
have_invisible = seaf_repo_manager_include_invisible_perm (seaf->repo_mgr, repo->id);
server_info = seaf_sync_manager_get_server_info (seaf->sync_mgr, repo->server, repo->user);
if (!server_info) {
seaf_warning ("Failed to get current server info.\n");
goto out;
}
if (repo->encrypted && repo->is_passwd_set)
file_handle->crypt = seafile_crypt_new (repo->enc_version, repo->enc_key, repo->enc_iv);
if (repo_tree_stat_path (repo->tree, file_path, &st) < 0) {
seaf_warning ("Failed to stat repo tree path %s in repo %s.\n",
file_path, repo_id);
goto out;
}
file_handle->start_download_time = (gint64)time(NULL);
if (!seaf_fs_manager_object_exists (seaf->fs_mgr,
repo->id,
repo->version,
st.id)) {
if (http_tx_manager_get_fs_object (seaf->http_tx_mgr,
server_info->host,
server_info->use_fileserver_port,
repo->token,
repo->id,
st.id,
file_path) < 0) {
seaf_warning ("Failed to get file object %s of %s from server %s.\n",
st.id, file_key, server_info->host);
goto out;
}
}
file = seaf_fs_manager_get_seafile (seaf->fs_mgr, repo->id, repo->version, st.id);
if (!file) {
seaf_warning ("Failed to get file object %s in repo %s.\n",
st.id, repo_id);
goto out;
}
if (file->file_size >= CACHE_BLOCK_MAP_THRESHOLD) {
pthread_rwlock_rdlock (&priv->block_map_lock);
block_map = g_hash_table_lookup (priv->block_map_cache, st.id);
pthread_rwlock_unlock (&priv->block_map_lock);
if (!block_map) {
if (http_tx_manager_get_file_block_map (seaf->http_tx_mgr,
server_info->host,
server_info->use_fileserver_port,
repo->token,
repo->id,
st.id,
&block_map,
&n_blocks) == 0) {
if (n_blocks != file->n_blocks) {
seaf_warning ("Block number return from server does not match"
"seafile object. File-id is %s. File is %s"
"Returned %d, expect %d\n",
st.id, file_key, n_blocks, file->n_blocks);
} else {
pthread_rwlock_wrlock (&priv->block_map_lock);
g_hash_table_replace (priv->block_map_cache, g_strdup(st.id), block_map);
pthread_rwlock_unlock (&priv->block_map_lock);
}
} else {
seaf_warning ("Failed to get block map for file object %s of %s "
"server %s.\n",
st.id, file_key, server_info->host);
}
}
if (block_map) {
calculate_block_offset (file, block_map, file_handle,
&block_offset, &file_offset);
}
}
seaf_sync_manager_update_active_path (seaf->sync_mgr, repo_id,
file_path, st.mode, SYNC_STATUS_SYNCING);
file_handle->cached_file->downloaded = 0;
file_handle->cached_file->total_download = file->file_size;
if (file_offset > 0) {
seaf_util_lseek (file_handle->fd, file_offset, SEEK_SET);
}
if (have_invisible && !file_handle->crypt) {
if (get_file_from_server (server_info, repo, file_path, block_offset, file_handle) < 0) {
goto out;
}
} else {
int i = block_offset;
if (file_handle->crypt) {
for (; i < file->n_blocks; i++) {
http_status = 200;
memset (&file_handle->blk_buffer, 0, sizeof(file_handle->blk_buffer));
if (http_tx_manager_get_block (seaf->http_tx_mgr, server_info->host,
server_info->use_fileserver_port, repo->token,
repo->id, file->blk_sha1s[i],
get_encrypted_block_cb, file_handle,
&http_status) < 0) {
if (!file_handle->fetch_canceled && !file_handle->cached_file->force_canceled)
seaf_warning ("Failed to get block %s of %s from server %s.\n",
file->blk_sha1s[i], file_key, server_info->host);
seaf_sync_manager_delete_active_path (seaf->sync_mgr, repo_id, file_path);
if (file_handle->notified_download_start)
send_file_download_notification ("file-download.stop", repo_id, file_path);
goto out;
}
fill_block (file_handle->blk_buffer.content, file_handle->blk_buffer.size, 1, file_handle);
g_free (file_handle->blk_buffer.content);
}
} else {
for (; i < file->n_blocks; i++) {
http_status = 200;
if (http_tx_manager_get_block (seaf->http_tx_mgr, server_info->host,
server_info->use_fileserver_port, repo->token,
repo->id, file->blk_sha1s[i],
get_block_cb, file_handle,
&http_status) < 0) {
if (!file_handle->fetch_canceled && !file_handle->cached_file->force_canceled)
seaf_warning ("Failed to get block %s of %s from server %s.\n",
file->blk_sha1s[i], file_key, server_info->host);
seaf_sync_manager_delete_active_path (seaf->sync_mgr, repo_id, file_path);
if (file_handle->notified_download_start)
send_file_download_notification ("file-download.stop", repo_id, file_path);
goto out;
}
}
}
}
if (file_handle->cached_file->downloaded != file->file_size) {
seaf_warning("Failed to download file %s in repo %s.\n", file_path, repo_id);
seaf_sync_manager_delete_active_path(seaf->sync_mgr, repo_id, file_path);
seaf_util_unlink (ondisk_path);
goto out;
}
pthread_mutex_lock (&priv->downloaded_files_lock);
FileDownloadedInfo *info = g_new0 (FileDownloadedInfo, 1);
info->file_path = g_build_filename (file_handle->cached_file->repo_uname,
file_path, NULL);
info->server = g_strdup (repo->server);
info->user = g_strdup (repo->user);
g_queue_push_head (priv->downloaded_files, info);
if (priv->downloaded_files->length > MAX_GET_FINISHED_FILES) {
file_downloaded_info_free (g_queue_pop_tail (priv->downloaded_files));
}
pthread_mutex_unlock (&priv->downloaded_files_lock);
/* Must close file handle (thus the fd of this handle) before updating file
* mtime. On Windows, closing an fd will update file mtime.
*/
gboolean notified_download_start = file_handle->notified_download_start;
free_cached_file_handle (file_handle);
file_handle = NULL;
if (mark_file_cached (ondisk_path, &st) == 0) {
seaf_sync_manager_update_active_path (seaf->sync_mgr, repo_id,
file_path, st.mode, SYNC_STATUS_SYNCED);
if (notified_download_start)
send_file_download_notification ("file-download.done", repo_id, file_path);
}
/* File content must be on the server when it's first downloaded. */
file_cache_mgr_set_file_uploaded (seaf->file_cache_mgr,
repo_id, file_path, TRUE);
out:
/* If server returns 5xx error, the error response will be written into the
* file contents. In this case, remove the "corrupted" cached file.
*/
if (http_status >= 500) {
seaf_util_unlink (ondisk_path);
}
g_strfreev (key_comps);
g_free (ondisk_path);
if (server_info)
seaf_sync_manager_free_server_info (server_info);
if (repo)
seaf_repo_unref (repo);
if (file)
seafile_unref (file);
pthread_mutex_lock (&priv->task_lock);
g_hash_table_remove (priv->cache_tasks, file_key);
pthread_mutex_unlock (&priv->task_lock);
if (file_handle)
free_cached_file_handle (file_handle);
g_free (file_key);
}
typedef enum CachedFileStatus {
CACHED_FILE_STATUS_NOT_EXISTS = 0,
CACHED_FILE_STATUS_FETCHING,
CACHED_FILE_STATUS_CACHED,
CACHED_FILE_STATUS_OUTDATED,
} CachedFileStatus;
static CachedFileStatus
check_cached_file_status (FileCacheMgrPriv *priv,
CachedFile *file,
RepoTreeStat *st)
{
char *ondisk_path = NULL;
char mtime_attr[64], size_attr[64];
char file_id_attr[41];
gssize len;
SeafStat file_st;
char mtime_str[16], size_str[64];
CachedFileStatus ret = CACHED_FILE_STATUS_NOT_EXISTS;
ondisk_path = cached_file_ondisk_path (file);
if (!seaf_util_exists (ondisk_path)) {
ret = CACHED_FILE_STATUS_NOT_EXISTS;
goto out;
}
if (seaf_stat (ondisk_path, &file_st) < 0) {
seaf_warning ("Failed to stat file %s:%s.\n", file->file_key, strerror(errno));
ret = CACHED_FILE_STATUS_NOT_EXISTS;
goto out;
}
/* seafile-mtime attr is set after seafile-size and file-id attrs,
* so if seafile-mtime attr exists, the other two attrs should exist.
*/
len = seaf_getxattr (ondisk_path, SEAFILE_MTIME_ATTR,
mtime_attr, sizeof(mtime_attr));
if (len < 0) {
ret = CACHED_FILE_STATUS_FETCHING;
goto out;
}
len = seaf_getxattr (ondisk_path, SEAFILE_SIZE_ATTR,
size_attr, sizeof(size_attr));
if (len < 0) {
ret = CACHED_FILE_STATUS_FETCHING;
goto out;
}
len = seaf_getxattr (ondisk_path, SEAFILE_FILE_ID_ATTR,
file_id_attr, sizeof(file_id_attr));
if (len < 0) {
ret = CACHED_FILE_STATUS_FETCHING;
goto out;
}
if (strcmp (st->id, file_id_attr) == 0) {
/* Cached file is up-to-date. */
ret = CACHED_FILE_STATUS_CACHED;
goto out;
}
snprintf (mtime_str, sizeof(mtime_str),
"%"G_GINT64_FORMAT, (gint64)file_st.st_mtime);
snprintf (size_str, sizeof(size_str),
"%"G_GINT64_FORMAT, (gint64)file_st.st_size);
if (strcmp (mtime_str, mtime_attr) == 0 &&
strcmp (size_str, size_attr) == 0) {
// File id is changed but mtime is not changed, try to refetch the file
ret = CACHED_FILE_STATUS_OUTDATED;
goto out;
}
// The cached file is changed locally, while the internal ID of that file
// is also updated. This means a conflict situation. The syncing algorithm
// tries to avoid this conflict from happening by delaying update to internal
// file id. But if this happens, we try not to overwrite local changes.
ret = CACHED_FILE_STATUS_CACHED;
out:
g_free (ondisk_path);
return ret;
}
static int remove_file_attrs (const char *path);
static int
start_cache_task (FileCacheMgrPriv *priv, CachedFile *file, RepoTreeStat *st,
const char *server, const char *user)
{
char *ondisk_path = NULL;
int flags;
CachedFileHandle *handle = NULL;
int fd;
int ret = 0;
pthread_mutex_lock (&priv->task_lock);
/* Only one fetch task can be running for each file. */
if (!g_hash_table_lookup (priv->cache_tasks, file->file_key)) {
CachedFileStatus status = check_cached_file_status (priv, file, st);
if (status == CACHED_FILE_STATUS_CACHED) {
/* If the cached file is up-to-date, don't need to fetch again. */
goto out;
}
gboolean overwrite = FALSE;
if (status == CACHED_FILE_STATUS_NOT_EXISTS ||
status == CACHED_FILE_STATUS_OUTDATED) {
overwrite = TRUE;
}
/* If cached file is in "FETCHING" status, its download was
* interrupted by restart.
*/
ondisk_path = cached_file_ondisk_path (file);
if (status == CACHED_FILE_STATUS_OUTDATED) {
remove_file_attrs (ondisk_path);
}
flags = (O_WRONLY | O_CREAT);
if (overwrite)
flags |= O_TRUNC;
fd = seaf_util_create (ondisk_path, flags, 0664);
if (fd < 0) {
seaf_warning ("Failed to open file %s:%s.\n",
file->file_key, strerror (errno));
ret = -1;
goto out;
}
/* Only non-zero size file needs to be fetched from server. */
if (st->size != 0) {
handle = cached_file_handle_new ();
handle->cached_file = file;
handle->fd = fd;
handle->cached_file->total_download = st->size;
handle->server = g_strdup(server);
handle->user = g_strdup(user);
cached_file_ref (file);
g_hash_table_replace (priv->cache_tasks, g_strdup(file->file_key), handle);
g_thread_pool_push (priv->tpool, handle, NULL);
} else {
mark_file_cached (ondisk_path, st);
close (fd);
}
}
out:
pthread_mutex_unlock (&priv->task_lock);
g_free (ondisk_path);
return ret;
}
static int
make_parent_dir (CachedFile *cached_file)
{
char *file_path = NULL;
char *parent_dir = NULL;
int ret = 0;
file_path = cached_file_ondisk_path (cached_file);
parent_dir = g_path_get_dirname (file_path);
if (g_file_test (parent_dir, G_FILE_TEST_IS_DIR)) {
goto out;
}
// Make sure parent dir has been created
if (checkdir_with_mkdir (parent_dir) < 0) {
seaf_warning ("Failed to make parent dir %s: %s.\n", parent_dir, strerror (errno));
ret = -1;
}
out:
g_free (parent_dir);
g_free (file_path);
return ret;
}
static CachedFile *
get_cached_file (FileCacheMgrPriv *priv,
const char *repo_id,
const char *repo_uname,
const char *file_path,
const char *server,
const char *user,
RepoTreeStat *st)
{
CachedFile *cached_file = NULL;
char *file_key;
file_key = g_strconcat (repo_id, "/", file_path, NULL);
pthread_mutex_lock (&priv->cache_lock);
cached_file = g_hash_table_lookup (priv->cached_files, file_key);
if (!cached_file) {
cached_file = g_new0 (CachedFile, 1);
cached_file->file_key = file_key;
cached_file->repo_uname = g_strdup (repo_uname);
g_hash_table_replace (priv->cached_files, cached_file->file_key, cached_file);
/* Keep 1 open number for internal reference. */
cached_file_ref (cached_file);
} else {
g_free (file_key);
/* If a file fetch task was cancelled before, reset this flag when the file
* is opened again. Some applications retry the open operation after failure.
* We have to prevent opening the file for a while after its download was
* cancelled.
*/
gint64 now = (gint64)time(NULL);
if (now - cached_file->last_cancel_time < 2) {
pthread_mutex_unlock (&priv->cache_lock);
cached_file = NULL;
goto out;
}
cached_file->force_canceled = FALSE;
cached_file->last_cancel_time = 0;
}
cached_file_ref (cached_file);
pthread_mutex_unlock (&priv->cache_lock);
if (make_parent_dir (cached_file) < 0) {
cached_file_unref (cached_file);
cached_file = NULL;
goto out;
}
if (start_cache_task (priv, cached_file, st, server, user) < 0) {
seaf_warning ("Failed to start cache task for file %s in repo %s.\n",
file_path, repo_id);
cached_file_unref (cached_file);
cached_file = NULL;
}
out:
return cached_file;
}
void
file_cache_mgr_cache_file (FileCacheMgr *mgr, const char *repo_id, const char *path, RepoTreeStat *st)
{
FileCacheMgrPriv *priv = mgr->priv;
CachedFile *cached_file = NULL;
char *file_key;
SeafRepo *repo;
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo) {
seaf_warning ("Failed to find repo %s.\n", repo_id);
return;
}
file_key = g_strconcat (repo_id, "/", path, NULL);
pthread_mutex_lock (&priv->cache_lock);
cached_file = g_hash_table_lookup (priv->cached_files, file_key);
if (!cached_file) {
cached_file = g_new0 (CachedFile, 1);
cached_file->file_key = file_key;
cached_file->repo_uname = g_strdup (repo->repo_uname);
g_hash_table_replace (priv->cached_files, cached_file->file_key, cached_file);
/* Keep 1 open number for internal reference. */
cached_file_ref (cached_file);
} else {
g_free (file_key);
}
cached_file_ref (cached_file);
pthread_mutex_unlock (&priv->cache_lock);
if (make_parent_dir (cached_file) < 0) {
goto out;
}
if (start_cache_task (priv, cached_file, st, repo->server, repo->user) < 0) {
seaf_warning ("Failed to start cache task for file %s in repo %s.\n",
path, repo_id);
}
out:
cached_file_unref (cached_file);
seaf_repo_unref (repo);
}
static CachedFileHandle *
open_cached_file_handle (FileCacheMgrPriv *priv,
const char *repo_id,
const char *repo_uname,
const char *file_path,
const char *server,
const char *user,
RepoTreeStat *st,
int flags)
{
CachedFile *file = NULL;
CachedFileHandle *file_handle = NULL;
char *ondisk_path = NULL;
int fd;
file = get_cached_file (priv, repo_id, repo_uname, file_path, server, user, st);
if (!file) {
return NULL;
}
ondisk_path = cached_file_ondisk_path (file);
fd = seaf_util_open (ondisk_path, O_RDWR);
if (fd < 0) {
seaf_warning ("Failed to open file %s:%s.\n", ondisk_path, strerror (errno));
cached_file_unref (file);
goto out;
}
file_handle = cached_file_handle_new ();
file_handle->cached_file = file;
file_handle->fd = fd;
file_handle->file_size = st->size;
file_handle->server = g_strdup(server);
file_handle->user = g_strdup(user);
if (flags & O_RDONLY)
file_handle->is_readonly = TRUE;
out:
g_free (ondisk_path);
return file_handle;
}
CachedFileHandle *
file_cache_mgr_open (FileCacheMgr *mgr, const char *repo_id,
const char *file_path, int flags)
{
FileCacheMgrPriv *priv = mgr->priv;
SeafRepo *repo = NULL;
RepoTreeStat tree_stat;
CachedFileHandle *handle = NULL;
int rc;
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo) {
seaf_warning ("Failed to get repo %s.\n", repo_id);
goto out;
}
rc = repo_tree_stat_path (repo->tree, file_path, &tree_stat);
if (rc < 0) {
seaf_warning ("Failed to stat tree path %s in repo %s: %s.\n",
file_path, repo_id, strerror(-rc));
goto out;
}
/* If we need to fetch a file from server, but network or server was
* already down, return failure.
*/
if ((!file_cache_mgr_is_file_cached (mgr, repo_id, file_path) ||
file_cache_mgr_is_file_outdated (mgr, repo_id, file_path)) &&
tree_stat.size != 0 &&
seaf_sync_manager_is_server_disconnected (seaf->sync_mgr)) {
return NULL;
}
handle = open_cached_file_handle (priv, repo_id, repo->repo_uname,
file_path, repo->server, repo->user, &tree_stat, flags);
if (!handle) {
seaf_warning ("Failed to open handle to cached file %s in repo %s.\n",
file_path, repo_id);
}
out:
seaf_repo_unref (repo);
return handle;
}
void
file_cache_mgr_close_file_handle (CachedFileHandle *file_handle)
{
char *file_key = g_strdup(file_handle->cached_file->file_key);
free_cached_file_handle (file_handle);
cancel_fetch_task_by_file (seaf->file_cache_mgr, file_key);
g_free (file_key);
}
#define CHECK_CACHE_INTERVAL 100000 /* 100ms */
#define READ_CACHE_TIMEOUT 5000000 /* 5 seconds. */
#define WRITE_CACHE_TIMEOUT 5000000 /* 5 seconds. */
#define RANDOM_READ_THRESHOLD 100000 /* 100KB */
static gssize
get_file_range_from_server (const char *server, const char *user,
const char *repo_id, const char *path, char *buf,
guint64 offset, size_t size)
{
seaf_debug ("Get file range %"G_GUINT64_FORMAT"-%"G_GUINT64_FORMAT" of file %s/%s.\n",
offset, offset+size-1, repo_id, path);
SeafAccount *account = seaf_repo_manager_get_account (seaf->repo_mgr, server, user);
if (!account) {
seaf_warning ("Failed to get account %s %s.\n", server, user);
return -1;
}
gssize ret = http_tx_manager_get_file_range (seaf->http_tx_mgr,
account->server,
account->token,
repo_id,
path,
buf,
offset, size);
seaf_account_free (account);
if (ret < 0)
return -EIO;
else
return ret;
}
gssize
file_cache_mgr_read (FileCacheMgr *mgr, CachedFileHandle *handle,
char *buf, size_t size, guint64 offset)
{
CachedFile *file = handle->cached_file;
char *ondisk_path = NULL;
char *repo_id = NULL, *path = NULL;
char attr[64];
SeafStat st;
gssize ret;
int wait_time = 0;
SeafRepo *repo = NULL;
ondisk_path = cached_file_ondisk_path (file);
if (!seaf_util_exists (ondisk_path)) {
seaf_warning ("Cached file %s does not exist when read.\n", file->file_key);
ret = -EIO;
goto out;
}
file_cache_mgr_get_file_info_from_handle (mgr, handle, &repo_id, &path);
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo) {
seaf_warning ("Failed to get repo %s\n", repo_id);
ret = -EIO;
goto out;
}
/* Wait until the intended region is cached. */
while (1) {
if (seaf_getxattr (ondisk_path, SEAFILE_MTIME_ATTR, attr, sizeof(attr)) > 0)
break;
if (seaf_stat (ondisk_path, &st) < 0) {
seaf_warning ("Failed to stat cache file %s: %s.\n",
ondisk_path, strerror (errno));
ret = -EIO;
goto out;
}
if ((offset + (guint64)size <= (guint64)handle->file_size) &&
(offset + (guint64)size <= (guint64)st.st_size)) {
break;
}
if ((offset > (guint64)st.st_size) &&
(offset - (guint64)st.st_size > RANDOM_READ_THRESHOLD)) {
ret = get_file_range_from_server (repo->server, repo->user, repo_id, path, buf, offset, size);
goto out;
}
if (wait_time >= READ_CACHE_TIMEOUT) {
seaf_debug ("Read cache file %s timeout.\n", ondisk_path);
ret = -EIO;
goto out;
}
/* Sleep 100ms to wait the file to be cached. */
g_usleep (CHECK_CACHE_INTERVAL);
wait_time += CHECK_CACHE_INTERVAL;
}
pthread_mutex_lock (&handle->lock);
gint64 rc = seaf_util_lseek (handle->fd, (gint64)offset, SEEK_SET);
if (rc < 0) {
seaf_warning ("Failed to lseek file %s: %s.\n", ondisk_path, strerror(errno));
ret = -errno;
pthread_mutex_unlock (&handle->lock);
goto out;
}
ret = readn (handle->fd, buf, size);
if (ret < 0) {
seaf_warning ("Failed to read file %s: %s.\n", ondisk_path,
strerror (errno));
ret = -errno;
pthread_mutex_unlock (&handle->lock);
goto out;
}
pthread_mutex_unlock (&handle->lock);
out:
g_free (ondisk_path);
g_free (repo_id);
g_free (path);
return ret;
}
/* On Windows, read operation may be issued after close is called.
* So sometimes when we read, the cache task may have been canceled.
* To ensure the file will be cached, we try to start cache task
* for every read operation. Since we make sure there can be only one
* cache task running for each file, it won't cause problems.
*/
static CachedFile *
start_cache_task_before_read (FileCacheMgr *mgr,
const char *repo_id,
const char *path,
RepoTreeStat *st)
{
FileCacheMgrPriv *priv = mgr->priv;
char *file_key = NULL;
CachedFile *file = NULL;
SeafRepo *repo = NULL;
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo) {
seaf_warning ("Failed to find repo %s.\n", repo_id);
return NULL;
}
file_key = g_strconcat (repo_id, "/", path, NULL);
pthread_mutex_lock (&priv->cache_lock);
file = g_hash_table_lookup (priv->cached_files, file_key);
if (!file) {
pthread_mutex_unlock (&priv->cache_lock);
goto out;
}
cached_file_ref (file);
pthread_mutex_unlock (&priv->cache_lock);
start_cache_task (priv, file, st, repo->server, repo->user);
out:
seaf_repo_unref (repo);
g_free (file_key);
return file;
}
gssize
file_cache_mgr_read_by_path (FileCacheMgr *mgr,
const char *repo_id, const char *path,
char *buf, size_t size, guint64 offset)
{
SeafRepo *repo = NULL;
RepoTreeStat tree_st;
char *ondisk_path = NULL;
CachedFile *file = NULL;
char attr[64];
SeafStat st;
int fd;
gssize ret;
int wait_time = 0;
ondisk_path = g_build_path ("/", mgr->priv->base_path, repo_id, path, NULL);
if (!seaf_util_exists (ondisk_path)) {
seaf_warning ("Cached file %s/%s does not exist when read.\n", repo_id, path);
ret = -EIO;
goto out;
}
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo) {
seaf_warning ("Failed to get repo %s.\n", repo_id);
ret = -ENOENT;
goto out;
}
if (repo_tree_stat_path (repo->tree, path, &tree_st) < 0) {
ret = -ENOENT;
goto out;
}
file = start_cache_task_before_read (mgr, repo_id, path, &tree_st);
if (!file) {
ret = -EIO;
goto out;
}
/* Wait until the intended region is cached. */
while (1) {
if (seaf_getxattr (ondisk_path, SEAFILE_MTIME_ATTR, attr, sizeof(attr)) > 0)
break;
if (seaf_stat (ondisk_path, &st) < 0) {
seaf_warning ("Failed to stat cache file %s: %s.\n",
ondisk_path, strerror (errno));
ret = -EIO;
goto out;
}
if ((offset + (guint64)size <= (guint64)tree_st.size) &&
(offset + (guint64)size <= (guint64)st.st_size)) {
break;
}
if ((offset > (guint64)st.st_size) &&
(offset - (guint64)st.st_size > RANDOM_READ_THRESHOLD)) {
ret = get_file_range_from_server (repo->server, repo->user, repo_id, path, buf, offset, size);
goto out;
}
if (wait_time >= READ_CACHE_TIMEOUT) {
seaf_warning ("Read cache file %s timeout.\n", ondisk_path);
ret = -EIO;
goto out;
}
/* Sleep 100ms to wait the file to be cached. */
g_usleep (CHECK_CACHE_INTERVAL);
wait_time += CHECK_CACHE_INTERVAL;
}
fd = seaf_util_open (ondisk_path, O_RDONLY);
if (fd < 0) {
seaf_warning ("Failed to open cached file %s: %s\n",
ondisk_path, strerror(errno));
ret = -EIO;
goto out;
}
gint64 rc = seaf_util_lseek (fd, (gint64)offset, SEEK_SET);
if (rc < 0) {
seaf_warning ("Failed to lseek file %s: %s.\n", ondisk_path, strerror(errno));
ret = -errno;
close (fd);
goto out;
}
ret = readn (fd, buf, size);
if (ret < 0) {
seaf_warning ("Failed to read file %s: %s.\n", ondisk_path,
strerror (errno));
ret = -errno;
close (fd);
goto out;
}
close (fd);
out:
seaf_repo_unref (repo);
g_free (ondisk_path);
cached_file_unref (file);
return ret;
}
/* gssize */
/* file_cache_mgr_read_by_path (FileCacheMgr *mgr, */
/* const char *repo_id, const char *path, */
/* char *buf, size_t size, guint64 offset) */
/* { */
/* char *ondisk_path = NULL; */
/* char attr[64]; */
/* int fd; */
/* gssize ret; */
/* ondisk_path = g_build_path ("/", mgr->priv->base_path, repo_id, path, NULL); */
/* if (!seaf_util_exists (ondisk_path)) { */
/* seaf_warning ("Cached file %s/%s does not exist when read.\n", repo_id, path); */
/* ret = -EIO; */
/* goto out; */
/* } */
/* if (seaf_getxattr (ondisk_path, SEAFILE_MTIME_ATTR, attr, sizeof(attr)) < 0) { */
/* seaf_warning ("File %s/%s is not cached yet when read.\n", repo_id, path); */
/* ret = -EIO; */
/* goto out; */
/* } */
/* fd = seaf_util_open (ondisk_path, O_RDONLY); */
/* if (fd < 0) { */
/* seaf_warning ("Failed to open cached file %s: %s\n", */
/* ondisk_path, strerror(errno)); */
/* ret = -EIO; */
/* goto out; */
/* } */
/* gint64 rc = seaf_util_lseek (fd, (gint64)offset, SEEK_SET); */
/* if (rc < 0) { */
/* seaf_warning ("Failed to lseek file %s: %s.\n", ondisk_path, strerror(errno)); */
/* ret = -errno; */
/* close (fd); */
/* goto out; */
/* } */
/* ret = readn (fd, buf, size); */
/* if (ret < 0) { */
/* seaf_warning ("Failed to read file %s: %s.\n", ondisk_path, */
/* strerror (errno)); */
/* ret = -errno; */
/* close (fd); */
/* goto out; */
/* } */
/* close (fd); */
/* out: */
/* g_free (ondisk_path); */
/* return ret; */
/* } */
gssize
file_cache_mgr_write (FileCacheMgr *mgr, CachedFileHandle *handle,
const char *buf, size_t size, off_t offset)
{
CachedFile *file = handle->cached_file;
char *ondisk_path = NULL;
char attr[64];
gssize ret = 0;
int wait_time = 0;
ondisk_path = cached_file_ondisk_path (file);
if (!seaf_util_exists (ondisk_path)) {
seaf_warning ("Cached file %s does not exist when write.\n", file->file_key);
ret = -EIO;
goto out;
}
while (1) {
if (seaf_getxattr (ondisk_path, SEAFILE_MTIME_ATTR, attr, sizeof(attr)) > 0)
break;
if (wait_time >= WRITE_CACHE_TIMEOUT) {
seaf_warning ("Write cache file %s timeout.\n", ondisk_path);
ret = -EIO;
goto out;
}
g_usleep (CHECK_CACHE_INTERVAL);
wait_time += CHECK_CACHE_INTERVAL;
}
pthread_mutex_lock (&handle->lock);
gint64 rc;
if (offset >= 0)
rc = seaf_util_lseek (handle->fd, offset, SEEK_SET);
else
rc = seaf_util_lseek (handle->fd, 0, SEEK_END);
if (rc < 0) {
seaf_warning ("Failed to lseek file %s: %s.\n", ondisk_path, strerror(errno));
ret = -errno;
pthread_mutex_unlock (&handle->lock);
goto out;
}
ret = writen (handle->fd, buf, size);
if (ret < 0) {
seaf_warning ("Failed to write file %s: %s.\n", ondisk_path,
strerror (errno));
ret = -EIO;
pthread_mutex_unlock (&handle->lock);
goto out;
}
pthread_mutex_unlock (&handle->lock);
out:
g_free (ondisk_path);
return ret;
}
static int
fetch_and_truncate_file (FileCacheMgr *mgr,
const char *repo_id,
const char *file_path,
const char *fullpath,
off_t length)
{
CachedFileHandle *handle;
char attr[64];
handle = file_cache_mgr_open (mgr, repo_id, file_path, 0);
if (!handle) {
return -EIO;
}
while (1) {
if (seaf_getxattr (fullpath, SEAFILE_MTIME_ATTR, attr, sizeof(attr)) > 0)
break;
g_usleep (CHECK_CACHE_INTERVAL);
}
file_cache_mgr_close_file_handle (handle);
int ret = seaf_truncate (fullpath, length);
if (ret < 0) {
ret = -errno;
seaf_warning ("Failed to truncate %s: %s.\n", fullpath, strerror(-ret));
}
return ret;
}
int
file_cache_mgr_truncate (FileCacheMgr *mgr,
const char *repo_id,
const char *file_path,
off_t length,
gboolean *not_cached)
{
FileCacheMgrPriv *priv = mgr->priv;
char *fullpath = NULL;
char attr[64];
int ret = 0;
if (not_cached)
*not_cached = FALSE;
fullpath = g_build_filename (priv->base_path, repo_id, file_path, NULL);
if (!seaf_util_exists (fullpath)) {
/* We don't need to do anything if the file is not cached and the file
* is going to be truncated to zero size.
*/
if (length != 0) {
ret = fetch_and_truncate_file (mgr, repo_id, file_path, fullpath, length);
} else {
if (not_cached)
*not_cached = TRUE;
}
goto out;
}
/* If cache file exists, there are two situations:
* 1. file is still being fetched;
* 2. file is already cached.
* We need to wait until file is cached. Otherwise we'll interfere other
* processes from reading/writing the file.
* Usually case 2 is true. So we can immediately truncate the file.
*/
while (1) {
if (seaf_getxattr (fullpath, SEAFILE_MTIME_ATTR, attr, sizeof(attr)) > 0)
break;
g_usleep (CHECK_CACHE_INTERVAL);
}
ret = seaf_truncate (fullpath, length);
if (ret < 0) {
ret = -errno;
seaf_warning ("Failed to truncate %s: %s.\n", fullpath, strerror(-ret));
}
out:
g_free (fullpath);
return ret;
}
int
file_cache_mgr_unlink (FileCacheMgr *mgr, const char *repo_id,
const char *file_path)
{
FileCacheMgrPriv *priv = mgr->priv;
char *file_key = g_strconcat (repo_id, "/", file_path, NULL);
char *ondisk_path = NULL;
pthread_mutex_lock (&priv->cache_lock);
/* Removing cached_file from the hash table will decrease n_open by 1.
* When all open handle to this file is closed, the cached file structure
* will be freed.
* If later a file with the same name is created and opened, a new cache file
* structure will be allocated.
*/
g_hash_table_remove (priv->cached_files, file_key);
/* The cached file on disk cannot be found after deletion. Access to the
* old file path will fail since read/write will check extended attrs of
* the ondisk cached file. This is incompatible with POSIX semantics but
* is simpler to implement. We don't have to track changes to file paths
* in this way.
*/
ondisk_path = g_build_filename (priv->base_path, repo_id, file_path, NULL);
int ret = seaf_util_unlink (ondisk_path);
if (ret < 0 && errno != ENOENT) {
ret = -errno;
seaf_warning ("Failed to unlink %s: %s.\n", ondisk_path, strerror(-ret));
}
g_free (ondisk_path);
pthread_mutex_unlock (&priv->cache_lock);
g_free (file_key);
return 0;
}
int
file_cache_mgr_rename (FileCacheMgr *mgr,
const char *old_repo_id,
const char *old_path,
const char *new_repo_id,
const char *new_path)
{
FileCacheMgrPriv *priv = mgr->priv;
char *ondisk_path_old = NULL, *ondisk_path_new = NULL;
char *new_path_parent = NULL;
int ret = 0;
/* We don't change the file paths in the CachedFile structures. Instead we
* just rename the ondisk cached file paths. The in-memory CachedFile structures
* will remain "dangling" and will fail to access files. They'll be cleaned up
* when we clean cached files.
*/
ondisk_path_old = g_build_filename (priv->base_path, old_repo_id, old_path, NULL);
ondisk_path_new = g_build_filename (priv->base_path, new_repo_id, new_path, NULL);
if (!seaf_util_exists (ondisk_path_old)) {
goto out;
}
new_path_parent = g_path_get_dirname (ondisk_path_new);
if (checkdir_with_mkdir (new_path_parent) < 0) {
seaf_warning ("[cache] Failed to create path %s: %s\n", new_path_parent, strerror(errno));
ret = -1;
goto out;
}
ret = seaf_util_rename (ondisk_path_old, ondisk_path_new);
if (ret < 0) {
if (errno == EACCES) {
if (seaf_util_unlink (ondisk_path_new) < 0) {
seaf_warning ("[cache] Failed to unlink %s before rename: %s.\n",
ondisk_path_new, strerror(errno));
}
ret = seaf_util_rename (ondisk_path_old, ondisk_path_new);
if (ret < 0) {
ret = -errno;
seaf_warning ("[cache] Failed to rename %s to %s: %s\n",
ondisk_path_old, ondisk_path_new, strerror(errno));
}
}
}
out:
g_free (ondisk_path_old);
g_free (ondisk_path_new);
g_free (new_path_parent);
return ret;
}
int
file_cache_mgr_mkdir (FileCacheMgr *mgr, const char *repo_id, const char *dir)
{
FileCacheMgrPriv *priv = mgr->priv;
char *ondisk_path = g_build_filename (priv->base_path, repo_id, dir, NULL);
int ret = checkdir_with_mkdir (ondisk_path);
if (ret < 0) {
ret = -errno;
seaf_warning ("[cache] Failed to create dir %s: %s\n", ondisk_path, strerror(errno));
}
g_free (ondisk_path);
return ret;
}
int
file_cache_mgr_rmdir (FileCacheMgr *mgr, const char *repo_id, const char *dir)
{
FileCacheMgrPriv *priv = mgr->priv;
char *ondisk_path = g_build_filename (priv->base_path, repo_id, dir, NULL);
int ret = seaf_util_rmdir (ondisk_path);
if (ret < 0) {
ret = -errno;
seaf_warning ("Failed to rmdir %s: %s.\n", ondisk_path, strerror(-ret));
}
g_free (ondisk_path);
return 0;
}
int
file_cache_mgr_stat_handle (FileCacheMgr *mgr,
CachedFileHandle *handle,
FileCacheStat *st)
{
SeafStat file_st;
int ret = 0;
ret = seaf_fstat (handle->fd, &file_st);
if (ret < 0) {
return ret;
}
st->mtime = file_st.st_mtime;
st->size = file_st.st_size;
return 0;
}
int
file_cache_mgr_stat (FileCacheMgr *mgr,
const char *repo_id,
const char *path,
FileCacheStat *st)
{
char *fullpath;
SeafStat file_st;
int ret = 0;
fullpath = g_build_filename (mgr->priv->base_path, repo_id, path, NULL);
ret = seaf_stat (fullpath, &file_st);
if (ret < 0) {
g_free (fullpath);
return ret;
}
st->mtime = file_st.st_mtime;
st->size = file_st.st_size;
g_free (fullpath);
return 0;
}
int
file_cache_mgr_set_attrs (FileCacheMgr *mgr,
const char *repo_id,
const char *path,
gint64 mtime,
gint64 size,
const char *file_id)
{
char *fullpath = g_build_filename (mgr->priv->base_path, repo_id, path, NULL);
char attr[64];
int len;
if (!seaf_util_exists (fullpath)) {
seaf_warning ("File %s does not exist while trying to set its attr.\n",
fullpath);
g_free (fullpath);
return -1;
}
if (seaf_setxattr (fullpath, SEAFILE_FILE_ID_ATTR, file_id, 41) < 0) {
seaf_warning ("Failed to set file-id attr for %s.\n", fullpath);
g_free (fullpath);
return -1;
}
len = snprintf (attr, sizeof(attr), "%"G_GINT64_FORMAT, size);
if (seaf_setxattr (fullpath, SEAFILE_SIZE_ATTR, attr, len+1) < 0) {
seaf_warning ("Failed to set seafile-size attr for %s.\n", fullpath);
g_free (fullpath);
return -1;
}
len = snprintf (attr, sizeof(attr), "%"G_GINT64_FORMAT, mtime);
if (seaf_setxattr (fullpath, SEAFILE_MTIME_ATTR, attr, len+1) < 0) {
seaf_warning ("Failed to set seafile-mtime attr for %s.\n", fullpath);
g_free (fullpath);
return -1;
}
g_free (fullpath);
return 0;
}
static int
get_file_attrs (const char *path, FileCacheStat *st)
{
char attr[64];
gint64 mtime, size;
char file_id[41];
gboolean uploaded;
if (seaf_getxattr (path, SEAFILE_MTIME_ATTR, attr, sizeof(attr)) < 0) {
seaf_debug ("Failed to get seafile-mtime attr of %s.\n", path);
return -1;
}
mtime = strtoll (attr, NULL, 10);
if (seaf_getxattr (path, SEAFILE_SIZE_ATTR, attr, sizeof(attr)) < 0) {
seaf_debug ("Failed to get seafile-size attr of %s.\n", path);
return -1;
}
size = strtoll (attr, NULL, 10);
if (seaf_getxattr (path, SEAFILE_FILE_ID_ATTR, file_id, sizeof(file_id)) < 0) {
seaf_debug ("Failed to get file-id attr of %s.\n", path);
return -1;
}
if (seaf_getxattr (path, SEAFILE_UPLOADED_ATTR, attr, sizeof(attr)) < 0) {
seaf_debug ("Failed to get uploaded attr of %s.\n", path);
uploaded = FALSE;
}
if (g_strcmp0(attr, "true") == 0)
uploaded = TRUE;
else
uploaded = FALSE;
st->mtime = mtime;
st->size = size;
memcpy (st->file_id, file_id, 40);
st->is_uploaded = uploaded;
return 0;
}
int
file_cache_mgr_get_attrs (FileCacheMgr *mgr,
const char *repo_id,
const char *path,
FileCacheStat *st)
{
char *fullpath = g_build_filename (mgr->priv->base_path, repo_id, path, NULL);
int ret = get_file_attrs (fullpath, st);
g_free (fullpath);
return ret;
}
static int
remove_file_attrs (const char *path)
{
seaf_removexattr (path, SEAFILE_MTIME_ATTR);
seaf_removexattr (path, SEAFILE_SIZE_ATTR);
seaf_removexattr (path, SEAFILE_FILE_ID_ATTR);
return 0;
}
gboolean
file_cache_mgr_is_file_cached (FileCacheMgr *mgr,
const char *repo_id,
const char *path)
{
char *fullpath = g_build_filename (mgr->priv->base_path, repo_id, path, NULL);
char attr[64];
gboolean ret = (seaf_getxattr(fullpath, SEAFILE_MTIME_ATTR, attr, sizeof(attr)) > 0);
g_free (fullpath);
return ret;
}
gboolean
file_cache_mgr_is_file_outdated (FileCacheMgr *mgr,
const char *repo_id,
const char *path)
{
char *fullpath = g_build_filename (mgr->priv->base_path, repo_id, path, NULL);
char file_id_attr[41];
SeafRepo *repo = NULL;
int len;
RepoTreeStat st;
gboolean ret;
memset (file_id_attr, 0, sizeof (file_id_attr));
len = seaf_getxattr (fullpath, SEAFILE_FILE_ID_ATTR,
file_id_attr, sizeof(file_id_attr));
if (len < 0) {
ret = FALSE;
goto out;
}
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo) {
seaf_warning ("Failed to get repo %.8s.\n", repo_id);
ret = FALSE;
goto out;
}
if (repo_tree_stat_path (repo->tree, path, &st) < 0) {
seaf_warning ("Failed to stat repo tree path %s in repo %s.\n",
path, repo_id);
ret = FALSE;
goto out;
}
ret = (strcmp (file_id_attr, st.id) != 0);
out:
g_free (fullpath);
if (repo)
seaf_repo_unref (repo);
return ret;
}
int
file_cache_mgr_set_file_uploaded (FileCacheMgr *mgr,
const char *repo_id,
const char *path,
gboolean uploaded)
{
char *fullpath = g_build_filename (mgr->priv->base_path, repo_id, path, NULL);
const char *attr;
int ret = 0;
if (!seaf_util_exists (fullpath)) {
g_free (fullpath);
return -1;
}
if (uploaded)
attr = "true";
else
attr = "false";
if (seaf_setxattr (fullpath, SEAFILE_UPLOADED_ATTR, attr, strlen(attr)+1) < 0)
ret = -1;
g_free (fullpath);
return ret;
}
gboolean
file_cache_mgr_is_file_uploaded (FileCacheMgr *mgr,
const char *repo_id,
const char *path,
const char *file_id)
{
char *fullpath = g_build_filename (mgr->priv->base_path, repo_id, path, NULL);
char attr[64];
gboolean ret = FALSE;
SeafRepo *repo = NULL;
SeafBranch *master = NULL;
SeafCommit *commit = NULL;
SeafDirent *dent = NULL;
if (seaf_getxattr (fullpath, SEAFILE_UPLOADED_ATTR, attr, sizeof(attr)) < 0)
ret = FALSE;
if (g_strcmp0 (attr, "true") == 0)
ret = TRUE;
else {
ret = FALSE;
repo = seaf_repo_manager_get_repo(seaf->repo_mgr, repo_id);
if (!repo) {
seaf_warning ("Failed to get repo %.8s.\n", repo_id);
goto out;
}
master = seaf_branch_manager_get_branch (seaf->branch_mgr, repo_id, "master");
if (!master) {
seaf_warning ("No master branch found for repo %s(%.8s).\n",
repo->name, repo_id);
goto out;
}
commit = seaf_commit_manager_get_commit (seaf->commit_mgr,
repo_id, repo->version,
master->commit_id);
if (!commit) {
seaf_warning ("Failed to get commit %.8s for repo %.8s.\n", repo->head->commit_id, repo_id);
goto out;
}
dent = seaf_fs_manager_get_dirent_by_path(seaf->fs_mgr,
repo_id,
repo->version,
commit->root_id,
path,
NULL);
if (!dent) {
seaf_warning ("Failed to get path %s for repo %.8s.\n", path, repo_id);
goto out;
}
if (strcmp (file_id, dent->id) == 0)
ret = TRUE;
}
out:
seaf_repo_unref (repo);
seaf_branch_unref (master);
seaf_commit_unref (commit);
seaf_dirent_free (dent);
g_free (fullpath);
return ret;
}
gboolean
file_cache_mgr_is_file_opened (FileCacheMgr *mgr,
const char *repo_id,
const char *path)
{
char *file_key;
CachedFile *file;
gboolean ret = FALSE;
file_key = g_strconcat (repo_id, "/", path, NULL);
pthread_mutex_lock (&mgr->priv->cache_lock);
file = g_hash_table_lookup (mgr->priv->cached_files, file_key);
if (!file)
goto out;
if (file->n_open > 1)
ret = TRUE;
out:
pthread_mutex_unlock (&mgr->priv->cache_lock);
g_free (file_key);
return ret;
}
gboolean
file_cache_mgr_is_file_changed (FileCacheMgr *mgr,
const char *repo_id,
const char *path,
gboolean print_msg)
{
FileCacheStat st, attrs;
char *fullpath;
fullpath = g_build_filename (mgr->priv->base_path, repo_id, path, NULL);
if (!seaf_util_exists (fullpath)) {
g_free (fullpath);
return FALSE;
}
g_free (fullpath);
/* Assume unchanged if failed to access file attributes. */
if (file_cache_mgr_stat (mgr, repo_id, path, &st) < 0)
return FALSE;
if (file_cache_mgr_get_attrs (mgr, repo_id, path, &attrs) < 0)
return FALSE;
if (st.mtime != attrs.mtime || st.size != attrs.size) {
if (print_msg) {
seaf_message ("File %s in repo %s is changed in file cache."
"Cached file mtime is %"G_GINT64_FORMAT", size is %"G_GINT64_FORMAT". "
"Seafile mtime is %"G_GINT64_FORMAT", size is %"G_GINT64_FORMAT".\n",
path, repo_id,
st.mtime, st.size, attrs.mtime, attrs.size);
}
return TRUE;
}
return FALSE;
}
void
file_cache_mgr_get_file_info_from_handle (FileCacheMgr *mgr,
CachedFileHandle *handle,
char **repo_id,
char **file_path)
{
CachedFile *file = handle->cached_file;
char **tokens = g_strsplit (file->file_key, "/", 2);
*repo_id = g_strdup(tokens[0]);
*file_path = g_strdup(tokens[1]);
g_strfreev (tokens);
}
int
file_cache_mgr_index_file (FileCacheMgr *mgr,
const char *repo_id,
int repo_version,
const char *file_path,
SeafileCrypt *crypt,
gboolean write_data,
unsigned char sha1[],
gboolean *changed)
{
char *fullpath = NULL;
SeafStat st;
char mtime_attr[64], size_attr[64];
char mtime_str[64], size_str[64];
char file_id[41];
int ret = 0;
fullpath = g_build_filename (mgr->priv->base_path, repo_id, file_path, NULL);
if (seaf_stat (fullpath, &st) < 0) {
seaf_warning ("Failed to stat %s: %s.\n", fullpath, strerror(errno));
ret = -1;
goto out;
}
snprintf (mtime_str, sizeof(mtime_str), "%"G_GINT64_FORMAT, st.st_mtime);
if (seaf_getxattr (fullpath, SEAFILE_MTIME_ATTR, mtime_attr, sizeof(mtime_attr)) < 0) {
seaf_warning ("Failed to get seafile-mtime attr from %s.\n", fullpath);
ret = -1;
goto out;
}
snprintf (size_str, sizeof(size_str), "%"G_GINT64_FORMAT, (gint64)st.st_size);
if (seaf_getxattr (fullpath, SEAFILE_SIZE_ATTR, size_attr, sizeof(size_attr)) < 0) {
seaf_warning ("Failed to get seafile-size attr from %s.\n", fullpath);
ret = -1;
goto out;
}
if (seaf_getxattr (fullpath, SEAFILE_FILE_ID_ATTR, file_id, sizeof(file_id)) < 0) {
seaf_warning ("Failed to get file-id attr from %s.\n", fullpath);
ret = -1;
goto out;
}
if (strcmp(mtime_attr, mtime_str) == 0 && strcmp(size_attr, size_str) == 0) {
/* File is not changed. */
*changed = FALSE;
hex_to_rawdata (file_id, sha1, 20);
goto out;
}
*changed = TRUE;
seaf_debug ("index %s\n", file_path);
ret = seaf_fs_manager_index_blocks (seaf->fs_mgr,
repo_id,
repo_version,
fullpath,
crypt,
write_data,
sha1);
out:
g_free (fullpath);
return ret;
}
static int
traverse_dir_recursive (FileCacheMgr *mgr,
const char *repo_id,
const char *path,
FileCacheTraverseCB file_cb,
FileCacheTraverseCB dir_cb,
void *user_data)
{
FileCacheMgrPriv *priv = mgr->priv;
SeafStat st;
char *dir_path = NULL, *subpath, *full_subpath;
const char *dname;
GDir *dir;
GError *error = NULL;
int ret = 0;
dir_path = g_build_filename (priv->base_path, repo_id, path, NULL);
dir = g_dir_open (dir_path, 0, &error);
if (!dir) {
seaf_warning ("Failed to open dir %s: %s.\n", dir_path, error->message);
ret = -1;
goto out;
}
while ((dname = g_dir_read_name (dir)) != NULL) {
if (path[0] != '\0')
subpath = g_build_filename (path, dname, NULL);
else
subpath = g_strdup(dname);
full_subpath = g_build_filename (dir_path, dname, NULL);
if (seaf_stat (full_subpath, &st) < 0) {
seaf_warning ("Failed to stat %s: %s.\n", full_subpath, strerror(errno));
g_free (subpath);
g_free (full_subpath);
continue;
}
if (S_ISDIR(st.st_mode)) {
if (dir_cb)
dir_cb (repo_id, subpath, &st, user_data);
ret = traverse_dir_recursive (mgr, repo_id, subpath, file_cb, dir_cb, user_data);
} else if (S_ISREG(st.st_mode)) {
if (file_cb)
file_cb (repo_id, subpath, &st, user_data);
}
g_free (full_subpath);
g_free (subpath);
}
g_dir_close (dir);
out:
g_free (dir_path);
return ret;
}
int
file_cache_mgr_traverse_path (FileCacheMgr *mgr,
const char *repo_id,
const char *path,
FileCacheTraverseCB file_cb,
FileCacheTraverseCB dir_cb,
void *user_data)
{
char *fullpath;
SeafStat st;
fullpath = g_build_filename (mgr->priv->base_path, repo_id, path, NULL);
if (!seaf_util_exists (fullpath)) {
g_free (fullpath);
return 0;
}
if (seaf_stat (fullpath, &st) < 0) {
seaf_warning ("Failed to stat %s: %s.\n", fullpath, strerror(errno));
g_free (fullpath);
return -1;
}
if (S_ISREG(st.st_mode)) {
if (file_cb)
file_cb (repo_id, path, &st, user_data);
} else if (S_ISDIR(st.st_mode)) {
if (dir_cb)
dir_cb (repo_id, path, &st, user_data);
traverse_dir_recursive (mgr, repo_id, path, file_cb, dir_cb, user_data);
}
g_free (fullpath);
return 0;
}
/* Clean up cached files. */
int
file_cache_mgr_set_clean_cache_interval (FileCacheMgr *mgr, int seconds)
{
mgr->priv->clean_cache_interval = seconds;
return seafile_session_config_set_int (seaf, KEY_CLEAN_CACHE_INTERVAL, seconds);
}
int
file_cache_mgr_get_clean_cache_interval (FileCacheMgr *mgr)
{
return mgr->priv->clean_cache_interval;
}
int
file_cache_mgr_set_cache_size_limit (FileCacheMgr *mgr, gint64 limit)
{
mgr->priv->cache_size_limit = limit;
return seafile_session_config_set_int64 (seaf, KEY_CACHE_SIZE_LIMIT, limit);
}
gint64
file_cache_mgr_get_cache_size_limit (FileCacheMgr *mgr)
{
return mgr->priv->cache_size_limit;
}
struct CachedFileInfo {
char repo_id[37];
char *path;
gint64 ctime;
gint64 size;
};
typedef struct CachedFileInfo CachedFileInfo;
static void
free_cached_file_info (CachedFileInfo *info)
{
if (!info)
return;
g_free (info->path);
g_free (info);
}
static gint
cached_file_compare_fn (gconstpointer a, gconstpointer b)
{
const CachedFileInfo *file_a = a;
const CachedFileInfo *file_b = b;
return file_a->ctime - file_b->ctime;
}
static void
load_cached_file_info_cb (const char *repo_id, const char *file_path,
SeafStat *st, void *user_data)
{
GList **pfiles = user_data;
CachedFileInfo *info = g_new0 (CachedFileInfo, 1);
memcpy (info->repo_id, repo_id, 36);
info->path = g_strdup(file_path);
info->ctime = (gint64)st->st_ctime;
info->size = (gint64)st->st_size;
*pfiles = g_list_prepend (*pfiles, info);
}
static GList *
load_cached_file_list (FileCacheMgr *mgr)
{
FileCacheMgrPriv *priv = mgr->priv;
GDir *dir;
GError *error = NULL;
const char *repo_id;
GList *ret = NULL;
dir = g_dir_open (priv->base_path, 0, &error);
if (error) {
seaf_warning ("Failed to open %s: %s.\n", priv->base_path, error->message);
return NULL;
}
while ((repo_id = g_dir_read_name (dir)) != NULL) {
if (!is_uuid_valid (repo_id))
continue;
file_cache_mgr_traverse_path (mgr, repo_id, "",
load_cached_file_info_cb, NULL, &ret);
}
g_dir_close (dir);
ret = g_list_sort (ret, cached_file_compare_fn);
return ret;
}
static gboolean
file_can_be_cleaned (FileCacheMgr *mgr, CachedFileInfo *info)
{
char *filename;
FileCacheStat st, attrs;
if (info->size == 0)
return FALSE;
filename = g_path_get_basename (info->path);
if (seaf_repo_manager_ignored_on_commit (filename)) {
seaf_debug ("[cache clean] Skip file %s/%s since it is ignored on commit.\n",
info->repo_id, info->path);
g_free (filename);
return FALSE;
}
g_free (filename);
/* Don't remove opened files. */
if (file_cache_mgr_is_file_opened (mgr, info->repo_id, info->path)) {
seaf_debug ("[cache clean] Skip file %s/%s since it is opened.\n",
info->repo_id, info->path);
return FALSE;
}
/* Don't remove changed files. */
if (file_cache_mgr_stat (mgr, info->repo_id, info->path, &st) < 0) {
seaf_debug ("[cache clean] Skip file %s/%s since cannot stat it: %s.\n",
info->repo_id, info->path, strerror(errno));
return FALSE;
}
/* But remove a file that's partially cached. */
if (file_cache_mgr_get_attrs (mgr, info->repo_id, info->path, &attrs) < 0) {
seaf_debug ("[cache clean] Remove partially cached file %s/%s\n",
info->repo_id, info->path);
return TRUE;
}
if (st.mtime != attrs.mtime || st.size != attrs.size) {
seaf_debug ("[cache clean] Skip file %s/%s since it is changed in cache. "
"st.mtime = %"G_GINT64_FORMAT", st.size = %"G_GINT64_FORMAT", "
"attrs.mtime = %"G_GINT64_FORMAT", attrs.size = %"G_GINT64_FORMAT"\n",
info->repo_id, info->path,
st.mtime, st.size, attrs.mtime, attrs.size);
return FALSE;
}
/* Don't remove not-uploaded files. */
if (!file_cache_mgr_is_file_uploaded (mgr, info->repo_id, info->path, attrs.file_id)) {
seaf_debug ("[cache clean] Skip file %s/%s since it is not uploaded.\n",
info->repo_id, info->path);
return FALSE;
}
return TRUE;
}
static void
do_clean_cache (FileCacheMgr *mgr)
{
GList *files = load_cached_file_list (mgr);
GList *ptr;
CachedFileInfo *info;
gint64 total_size = 0, target, removed_size = 0;
int n_files = 0, n_removed = 0;
for (ptr = files; ptr; ptr = ptr->next) {
info = (CachedFileInfo *)ptr->data;
total_size += info->size;
++n_files;
}
seaf_message ("cache size limit is %"G_GINT64_FORMAT"\n", mgr->priv->cache_size_limit);
if (total_size < mgr->priv->cache_size_limit) {
goto out;
}
seaf_message ("Cleaning cache space.\n");
seaf_message ("%d files in cache, total size is %"G_GINT64_FORMAT"\n",
n_files, total_size);
/* Clean until the total cache size reduce to 70% of limit. */
target = (total_size - mgr->priv->cache_size_limit * 0.7);
for (ptr = files; ptr; ptr = ptr->next) {
info = (CachedFileInfo *)ptr->data;
if (file_can_be_cleaned (mgr, info)) {
seaf_debug ("[cache clean] Removing %s/%s.\n", info->repo_id, info->path);
file_cache_mgr_unlink (mgr, info->repo_id, info->path);
seaf_sync_manager_delete_active_path (seaf->sync_mgr,
info->repo_id,
info->path);
removed_size += info->size;
++n_removed;
if (removed_size >= target)
break;
}
}
seaf_message ("Cache cleaning done.\n");
seaf_message ("Removed %d files, cleaned up %"G_GINT64_FORMAT" MB\n",
n_removed, (gint64)(removed_size/1000000));
out:
g_list_free_full (files, (GDestroyNotify)free_cached_file_info);
}
static void *
clean_cache_worker (void *data)
{
FileCacheMgr *mgr = data;
/* Delay 1 minute so that it doesn't conflict with repo tree loading. */
seaf_sleep (60);
while (1) {
do_clean_cache (mgr);
seaf_sleep (mgr->priv->clean_cache_interval);
}
return NULL;
}
static void
do_remove_deleted_cache (FileCacheMgr *mgr)
{
GDir *dir;
const char *dname;
GError *error = NULL;
char *top_dir_path;
char *deleted_path;
top_dir_path = mgr->priv->base_path;
dir = g_dir_open (top_dir_path, 0, &error);
if (error) {
seaf_warning ("Failed to open top file-cache dir: %s\n", error->message);
return;
}
while ((dname = g_dir_read_name (dir)) != NULL) {
if (strncmp (dname, "deleted", strlen("deleted")) == 0) {
seaf_message ("Removing file cache %s.\n", dname);
deleted_path = g_build_filename (top_dir_path, dname, NULL);
seaf_rm_recursive (deleted_path);
g_free (deleted_path);
}
}
g_dir_close (dir);
}
#define REMOVE_DELETED_CACHE_INTERVAL 30
static void *
remove_deleted_cache_worker (void *data)
{
FileCacheMgr *mgr = data;
while (1) {
do_remove_deleted_cache (mgr);
seaf_sleep (REMOVE_DELETED_CACHE_INTERVAL);
}
return NULL;
}
static char *
gen_deleted_cache_path (const char *base_path, const char *repo_id)
{
int n = 1;
char *path = NULL;
char *name = NULL;
name = g_strdup_printf ("deleted-%s", repo_id);
path = g_build_filename (base_path, name, NULL);
while (g_file_test(path, G_FILE_TEST_EXISTS) && n < 10) {
g_free (path);
name = g_strdup_printf ("deleted-%s(%d)", repo_id, n);
path = g_build_filename (base_path, name, NULL);
g_free (name);
++n;
}
if (n == 10) {
g_free (path);
return NULL;
}
return path;
}
int
file_cache_mgr_delete_repo_cache (FileCacheMgr *mgr, const char *repo_id)
{
char *cache_path = NULL, *deleted_path = NULL;
int ret;
cache_path = g_build_filename (mgr->priv->base_path, repo_id, NULL);
if (!seaf_util_exists (cache_path)) {
g_free (cache_path);
return 0;
}
deleted_path = gen_deleted_cache_path (mgr->priv->base_path, repo_id);
if (!deleted_path) {
seaf_warning ("Too many deleted cache dirs for repo %s.\n", repo_id);
g_free (cache_path);
return -1;
}
int n = 0;
while (1) {
ret = seaf_util_rename (cache_path, deleted_path);
if (ret < 0) {
seaf_warning ("Failed to rename file cache dir %s to %s: %s.\n",
cache_path, deleted_path, strerror(errno));
if (errno == EACCES && ++n < 3) {
seaf_warning ("Retry rename\n");
g_usleep (100000);
} else {
break;
}
} else {
break;
}
}
g_free (cache_path);
g_free (deleted_path);
return ret;
}
gboolean
file_cache_mgr_is_fetching_file (FileCacheMgr *mgr)
{
return (g_hash_table_size(mgr->priv->cache_tasks) != 0);
}
int
file_cache_mgr_utimen (FileCacheMgr *mgr, const char *repo_id,
const char *path, time_t mtime, time_t atime)
{
int ret = 0;
struct utimbuf times;
char *ondisk_path = g_build_filename (mgr->priv->base_path, repo_id, path, NULL);
times.actime = atime;
times.modtime = mtime;
ret = utime (ondisk_path, ×);
if (ret < 0) {
seaf_warning ("Failed to utime %s: %s\n", ondisk_path, strerror(errno));
ret = (-errno);
}
g_free (ondisk_path);
return ret;
}
static void
collect_downloading_files (gpointer key, gpointer value,
gpointer user_data)
{
CachedFileHandle *handle = value;
char *file_key = key;
char **tokens = g_strsplit (file_key, "/", 2);
char *path = tokens[1];
FileCacheMgrPriv *priv = seaf->file_cache_mgr->priv;
json_t *downloading = user_data;
json_t *info_obj = json_object ();
CachedFile *cached_file;
cached_file = g_hash_table_lookup (priv->cached_files, file_key);
char *abs_path = g_build_filename (cached_file->repo_uname, path, NULL);
json_object_set_string_member (info_obj, "server", handle->server);
json_object_set_string_member (info_obj, "username", handle->user);
json_object_set_string_member (info_obj, "file_path", abs_path);
json_object_set_int_member (info_obj, "downloaded", cached_file->downloaded);
json_object_set_int_member (info_obj, "total_download", cached_file->total_download);
json_array_append_new (downloading, info_obj);
g_free (abs_path);
g_strfreev (tokens);
}
json_t *
file_cache_mgr_get_download_progress (FileCacheMgr *mgr)
{
FileCacheMgrPriv *priv = mgr->priv;
int i = 0;
json_t *downloaded = json_array ();
json_t *downloading = json_array ();
json_t *progress = json_object ();
FileDownloadedInfo *info;
json_t *downloaded_obj;
pthread_mutex_lock (&priv->task_lock);
g_hash_table_foreach (priv->cache_tasks, collect_downloading_files,
downloading);
pthread_mutex_unlock (&priv->task_lock);
pthread_mutex_lock (&priv->downloaded_files_lock);
while (i < MAX_GET_FINISHED_FILES) {
info = g_queue_peek_nth (priv->downloaded_files, i);
if (info) {
downloaded_obj = json_object ();
json_object_set_string_member (downloaded_obj, "server", info->server);
json_object_set_string_member (downloaded_obj, "username", info->user);
json_object_set_string_member (downloaded_obj, "file_path", info->file_path);
json_array_append_new (downloaded, downloaded_obj);
} else {
break;
}
i++;
}
pthread_mutex_unlock (&priv->downloaded_files_lock);
json_object_set_new (progress, "downloaded_files", downloaded);
json_object_set_new (progress, "downloading_files", downloading);
return progress;
}
#if 0
#define DOWNLOADED_FILE_LIST_NAME "downloaded.json"
#define SAVE_DOWNLOADED_FILE_LIST_INTERVAL 5
#define DOWNLOADED_FILE_LIST_VERSION 1
static void *
save_downloaded_file_list_worker (void *data)
{
char *path = g_build_filename (seaf->seaf_dir, DOWNLOADED_FILE_LIST_NAME, NULL);
json_t *progress = NULL, *downloaded, *list;
char *txt = NULL;
GError *error = NULL;
while (1) {
seaf_sleep (SAVE_DOWNLOADED_FILE_LIST_INTERVAL);
progress = file_cache_mgr_get_download_progress (seaf->file_cache_mgr);
downloaded = json_object_get (progress, "downloaded_files");
if (json_array_size(downloaded) > 0) {
list = json_object();
json_object_set_new (list, "version",
json_integer(DOWNLOADED_FILE_LIST_VERSION));
json_object_set (list, "downloaded_files", downloaded);
txt = json_dumps (list, 0);
if (!g_file_set_contents (path, txt, -1, &error)) {
seaf_warning ("Failed to save downloaded file list: %s\n",
error->message);
g_clear_error (&error);
}
json_decref (list);
free (txt);
}
json_decref (progress);
}
g_free (path);
return NULL;
}
static void
load_downloaded_file_list (FileCacheMgr *mgr)
{
char *path = g_build_filename (seaf->seaf_dir, DOWNLOADED_FILE_LIST_NAME, NULL);
char *txt = NULL;
gsize len;
GError *error = NULL;
json_t *list = NULL, *downloaded = NULL;
json_error_t jerror;
if (!g_file_test (path, G_FILE_TEST_IS_REGULAR))
return;
if (!g_file_get_contents (path, &txt, &len, &error)) {
seaf_warning ("Failed to read downloaded file list: %s\n", error->message);
g_clear_error (&error);
g_free (path);
return;
}
list = json_loadb (txt, len, 0, &jerror);
if (!list) {
seaf_warning ("Failed to load downloaded file list: %s\n", jerror.text);
goto out;
}
if (json_typeof(list) != JSON_OBJECT) {
seaf_warning ("Bad downloaded file list format.\n");
goto out;
}
downloaded = json_object_get (list, "downloaded_files");
if (!downloaded) {
seaf_warning ("No downloaded_files in json object.\n");
goto out;
}
if (json_typeof(downloaded) != JSON_ARRAY) {
seaf_warning ("Bad downloaded file list format.\n");
goto out;
}
json_t *iter;
size_t n = json_array_size (downloaded), i;
for (i = 0; i < n; ++i) {
iter = json_array_get (downloaded, i);
if (json_typeof(iter) != JSON_STRING) {
seaf_warning ("Bad downloaded file list format.\n");
goto out;
}
g_queue_push_tail (mgr->priv->downloaded_files,
g_strdup(json_string_value(iter)));
}
out:
if (list)
json_decref (list);
g_free (path);
g_free (txt);
}
#endif
int
file_cache_mgr_cancel_download (FileCacheMgr *mgr,
const char *server,
const char *user,
const char *full_file_path,
GError **error)
{
char **tokens = NULL;
char *category = NULL, *repo_name = NULL, *file_path = NULL;
char *display_name = NULL;
char *repo_id = NULL;
char *file_key = NULL;
CachedFile *file;
int ret = 0;
tokens = g_strsplit (full_file_path, "/", 3);
if (g_strv_length (tokens) != 3) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
"invalid file path format.");
g_strfreev (tokens);
return -1;
}
category = tokens[0];
repo_name = tokens[1];
file_path = tokens[2];
display_name = g_build_path ("/", category, repo_name, NULL);
repo_id = seaf_repo_manager_get_repo_id_by_display_name (seaf->repo_mgr, server, user, display_name);
g_free (display_name);
if (!repo_id) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
"Repo not found.");
ret = -1;
goto out;
}
file_key = g_strconcat (repo_id, "/", file_path, NULL);
pthread_mutex_lock (&mgr->priv->task_lock);
if (!g_hash_table_lookup (mgr->priv->cache_tasks, file_key)) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
"%s is not downloading.", file_key);
pthread_mutex_unlock (&mgr->priv->task_lock);
ret = -1;
goto out;
}
pthread_mutex_unlock (&mgr->priv->task_lock);
pthread_mutex_lock (&mgr->priv->cache_lock);
file = g_hash_table_lookup (mgr->priv->cached_files, file_key);
if (file) {
file->force_canceled = TRUE;
file->last_cancel_time = (gint64)time(NULL);
}
pthread_mutex_unlock (&mgr->priv->cache_lock);
out:
g_strfreev (tokens);
g_free (repo_id);
g_free (file_key);
return ret;
}
#define BEFORE_NOTIFY_DOWNLOAD_START 5
static void *
check_download_file_time_worker (void *data)
{
FileCacheMgrPriv *priv = data;
gint64 now;
GHashTableIter iter;
gpointer key, value;
char *file_key;
CachedFileHandle *handle;
char **tokens;
char *repo_id, *file_path;
while (1) {
now = (gint64)time(NULL);
pthread_mutex_lock (&priv->task_lock);
g_hash_table_iter_init (&iter, priv->cache_tasks);
while (g_hash_table_iter_next (&iter, &key, &value)) {
file_key = key;
handle = value;
if (!handle->notified_download_start &&
(!handle->fetch_canceled || handle->cached_file->n_open > 2) &&
handle->start_download_time > 0 &&
(now - handle->start_download_time >= BEFORE_NOTIFY_DOWNLOAD_START)) {
tokens = g_strsplit (file_key, "/", 2);
repo_id = tokens[0];
file_path = tokens[1];
send_file_download_notification ("file-download.start", repo_id, file_path);
g_strfreev (tokens);
handle->notified_download_start = TRUE;
}
}
pthread_mutex_unlock (&priv->task_lock);
seaf_sleep (1);
}
return NULL;
}
#ifndef WIN32
static int
rm_file_and_active_path_recursive (const char *repo_id, const char *path, const char *active_path)
{
SeafStat st;
int ret = 0;
GDir *dir;
const char *dname;
char *subpath;
char *sub_active_path;
GError *error = NULL;
if (seaf_stat (path, &st) < 0) {
seaf_warning ("Failed to stat %s: %s\n", path, strerror(errno));
return -1;
}
if (S_ISREG(st.st_mode)) {
ret = seaf_util_unlink (path);
seaf_sync_manager_delete_active_path (seaf->sync_mgr, repo_id, active_path);
return ret;
} else if (S_ISDIR (st.st_mode)) {
dir = g_dir_open (path, 0, &error);
if (error) {
seaf_warning ("Failed to open dir %s: %s\n", path, error->message);
return -1;
}
while ((dname = g_dir_read_name (dir)) != NULL) {
subpath = g_build_filename (path, dname, NULL);
sub_active_path = g_build_filename (active_path, dname, NULL);
ret = rm_file_and_active_path_recursive (repo_id, subpath, sub_active_path);
g_free (subpath);
g_free (sub_active_path);
if (ret < 0)
break;
}
g_dir_close (dir);
if (ret == 0) {
ret = seaf_util_rmdir (path);
}
return ret;
}
return ret;
}
int
file_cache_mgr_uncache_path (const char *repo_id, const char *path)
{
FileCacheMgr *mgr = seaf->file_cache_mgr;
char *top_dir_path = mgr->priv->base_path;
char *deleted_path;
char *canon_path = NULL;
if (strcmp (path, "") != 0) {
canon_path = format_path (path);
} else {
canon_path = g_strdup(path);
}
deleted_path = g_build_filename (top_dir_path, repo_id, path, NULL);
rm_file_and_active_path_recursive (repo_id, deleted_path, canon_path);
g_free (deleted_path);
g_free (canon_path);
return 0;
}
#endif
seadrive-fuse-3.0.13/src/file-cache-mgr.h 0000664 0000000 0000000 00000016635 14761776747 0020110 0 ustar 00root root 0000000 0000000 #ifndef FILE_CACHE_MGR_H
#define FILE_CACHE_MGR_H
#include "utils.h"
#include "seafile-crypt.h"
struct FileCacheMgrPriv;
typedef struct FileCacheMgr {
struct FileCacheMgrPriv *priv;
} FileCacheMgr;
typedef struct _BlockBuffer {
char *content;
int size;
} BlockBuffer;
typedef struct CachedFileHandle {
struct CachedFile *cached_file;
pthread_mutex_t lock;
int fd;
SeafileCrypt *crypt;
BlockBuffer blk_buffer;
gint64 file_size;
gboolean is_readonly;
gboolean fetch_canceled;
gint64 start_download_time;
gboolean notified_download_start;
char *server;
char *user;
} CachedFileHandle;
FileCacheMgr *
file_cache_mgr_new (const char *parent_dir);
void
file_cache_mgr_init (FileCacheMgr *mgr);
void
file_cache_mgr_start (FileCacheMgr *mgr);
CachedFileHandle *
file_cache_mgr_open (FileCacheMgr *mgr, const char *repo_id,
const char *file_path, int flags);
void
file_cache_mgr_close_file_handle (CachedFileHandle *file_handle);
gboolean
cached_file_handle_is_readonly (CachedFileHandle *file_handle);
gssize
file_cache_mgr_read (FileCacheMgr *mgr, CachedFileHandle *handle,
char *buf, size_t size, guint64 offset);
gssize
file_cache_mgr_read_by_path (FileCacheMgr *mgr,
const char *repo_id, const char *path,
char *buf, size_t size, guint64 offset);
gssize
file_cache_mgr_write (FileCacheMgr *mgr, CachedFileHandle *handle,
const char *buf, size_t size, off_t offset);
gssize
file_cache_mgr_write_by_path (FileCacheMgr *mgr,
const char *repo_id, const char *path,
const char *buf, size_t size, off_t offset);
int
file_cache_mgr_truncate (FileCacheMgr *mgr,
const char *repo_id,
const char *path,
off_t length,
gboolean *not_cached);
/* This function should only be called after the file was deleted in fs tree. */
int
file_cache_mgr_unlink (FileCacheMgr *mgr,
const char *repo_id,
const char *file_path);
int
file_cache_mgr_rename (FileCacheMgr *mgr,
const char *old_repo_id,
const char *old_path,
const char *new_repo_id,
const char *new_path);
int
file_cache_mgr_mkdir (FileCacheMgr *mgr, const char *repo_id, const char *dir);
int
file_cache_mgr_rmdir (FileCacheMgr *mgr, const char *repo_id, const char *dir);
struct FileCacheStat {
char file_id[41];
gint64 mtime;
gint64 size;
gboolean is_uploaded;
};
typedef struct FileCacheStat FileCacheStat;
/* Get stat of the cached file. The stat info is from the local file system, not
* the extended attrs associated with the cached file.
*/
int
file_cache_mgr_stat_handle (FileCacheMgr *mgr,
CachedFileHandle *handle,
FileCacheStat *st);
int
file_cache_mgr_stat (FileCacheMgr *mgr,
const char *repo_id,
const char *path,
FileCacheStat *st);
gboolean
file_cache_mgr_is_file_cached (FileCacheMgr *mgr,
const char *repo_id,
const char *path);
gboolean
file_cache_mgr_is_file_outdated (FileCacheMgr *mgr,
const char *repo_id,
const char *path);
/* Update extended attrs of the cached file. */
int
file_cache_mgr_set_attrs (FileCacheMgr *mgr,
const char *repo_id,
const char *path,
gint64 mtime,
gint64 size,
const char *file_id);
int
file_cache_mgr_get_attrs (FileCacheMgr *mgr,
const char *repo_id,
const char *path,
FileCacheStat *st);
int
file_cache_mgr_set_file_uploaded (FileCacheMgr *mgr,
const char *repo_id,
const char *path,
gboolean uploaded);
gboolean
file_cache_mgr_is_file_uploaded (FileCacheMgr *mgr,
const char *repo_id,
const char *path,
const char *file_id);
gboolean
file_cache_mgr_is_file_opened (FileCacheMgr *mgr,
const char *repo_id,
const char *path);
gboolean
file_cache_mgr_is_file_changed (FileCacheMgr *mgr,
const char *repo_id,
const char *path,
gboolean print_msg);
/* Get repo id and path of the file from a handle. */
void
file_cache_mgr_get_file_info_from_handle (FileCacheMgr *mgr,
CachedFileHandle *handle,
char **repo_id,
char **file_path);
int
file_cache_mgr_index_file (FileCacheMgr *mgr,
const char *repo_id,
int repo_version,
const char *file_path,
SeafileCrypt *crypt,
gboolean write_data,
unsigned char sha1[],
gboolean *changed);
typedef void (*FileCacheTraverseCB) (const char *repo_id,
const char *file_path,
SeafStat *st,
void *user_data);
int
file_cache_mgr_traverse_path (FileCacheMgr *mgr,
const char *repo_id,
const char *path,
FileCacheTraverseCB file_cb,
FileCacheTraverseCB dir_cb,
void *user_data);
int
file_cache_mgr_set_clean_cache_interval (FileCacheMgr *mgr, int seconds);
int
file_cache_mgr_get_clean_cache_interval (FileCacheMgr *mgr);
int
file_cache_mgr_set_cache_size_limit (FileCacheMgr *mgr, gint64 limit);
gint64
file_cache_mgr_get_cache_size_limit (FileCacheMgr *mgr);
int
file_cache_mgr_delete_repo_cache (FileCacheMgr *mgr, const char *repo_id);
gboolean
file_cache_mgr_is_fetching_file (FileCacheMgr *mgr);
void
file_cache_mgr_cache_file (FileCacheMgr *mgr, const char *repo_id, const char *path, struct _RepoTreeStat *st);
int
file_cache_mgr_utimen (FileCacheMgr *mgr, const char *repo_id, const char *path, time_t mtime, time_t atime);
/* Functions that only works for files and folders under root directory.
* Except for library folders, the root directory can also contain some special
* files/folders created by the OS.
* These top-level files and folders are directly mapped into the "file-cache/root"
* folder.
*/
int
file_cache_mgr_uncache_path (const char *repo_id, const char *path);
// {"downloading_files": [{"file_path":, "downloaded":, "total_download":}, ], "downloaded_files": [ten latest downloaded files]}
json_t *
file_cache_mgr_get_download_progress (FileCacheMgr *mgr);
int
file_cache_mgr_cancel_download (FileCacheMgr *mgr,
const char *server,
const char *user,
const char *full_file_path, GError **error);
#endif
seadrive-fuse-3.0.13/src/filelock-mgr.c 0000664 0000000 0000000 00000041466 14761776747 0017713 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#include "common.h"
#include
#include "seafile-session.h"
#include "filelock-mgr.h"
#include "log.h"
#include "db.h"
struct _FilelockMgrPriv {
GHashTable *repo_locked_files;
pthread_mutex_t hash_lock;
sqlite3 *db;
pthread_mutex_t db_lock;
};
typedef struct _FilelockMgrPriv FilelockMgrPriv;
typedef struct _LockInfo {
int locked_by_me;
} LockInfo;
/* When a file is locked by me, it can have two reasons:
* - Locked by the user manually
* - Auto-Locked by Seafile when it detects Office opens the file.
*/
struct _SeafFilelockManager *
seaf_filelock_manager_new (struct _SeafileSession *session)
{
SeafFilelockManager *mgr = g_new0 (SeafFilelockManager, 1);
FilelockMgrPriv *priv = g_new0 (FilelockMgrPriv, 1);
mgr->session = session;
mgr->priv = priv;
priv->repo_locked_files = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free,
(GDestroyNotify)g_hash_table_destroy);
pthread_mutex_init (&priv->hash_lock, NULL);
pthread_mutex_init (&priv->db_lock, NULL);
return mgr;
}
static void
lock_info_free (LockInfo *info)
{
g_free (info);
}
static gboolean
load_locked_files (sqlite3_stmt *stmt, void *data)
{
GHashTable *repo_locked_files = data, *files;
const char *repo_id, *path;
int locked_by_me;
repo_id = (const char *)sqlite3_column_text (stmt, 0);
path = (const char *)sqlite3_column_text (stmt, 1);
locked_by_me = sqlite3_column_int (stmt, 2);
files = g_hash_table_lookup (repo_locked_files, repo_id);
if (!files) {
files = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)lock_info_free);
g_hash_table_insert (repo_locked_files, g_strdup(repo_id), files);
}
char *key = g_strdup(path);
LockInfo *info = g_new0 (LockInfo, 1);
info->locked_by_me = locked_by_me;
g_hash_table_replace (files, key, info);
return TRUE;
}
int
seaf_filelock_manager_init (SeafFilelockManager *mgr)
{
char *db_path;
sqlite3 *db;
char *sql;
db_path = g_build_filename (seaf->seaf_dir, "filelocks.db", NULL);
if (sqlite_open_db (db_path, &db) < 0)
return -1;
g_free (db_path);
mgr->priv->db = db;
sql = "CREATE TABLE IF NOT EXISTS ServerLockedFiles ("
"repo_id TEXT, path TEXT, locked_by_me INTEGER);";
sqlite_query_exec (db, sql);
sql = "CREATE INDEX IF NOT EXISTS server_locked_files_repo_id_idx "
"ON ServerLockedFiles (repo_id);";
sqlite_query_exec (db, sql);
sql = "CREATE TABLE IF NOT EXISTS ServerLockedFilesTimestamp ("
"repo_id TEXT, timestamp INTEGER, PRIMARY KEY (repo_id));";
sqlite_query_exec (db, sql);
sql = "SELECT repo_id, path, locked_by_me FROM ServerLockedFiles";
pthread_mutex_lock (&mgr->priv->db_lock);
pthread_mutex_lock (&mgr->priv->hash_lock);
if (sqlite_foreach_selected_row (mgr->priv->db, sql,
load_locked_files,
mgr->priv->repo_locked_files) < 0) {
pthread_mutex_unlock (&mgr->priv->db_lock);
pthread_mutex_unlock (&mgr->priv->hash_lock);
g_hash_table_destroy (mgr->priv->repo_locked_files);
return -1;
}
pthread_mutex_unlock (&mgr->priv->hash_lock);
pthread_mutex_unlock (&mgr->priv->db_lock);
return 0;
}
int
seaf_filelock_manager_start (SeafFilelockManager *mgr)
{
return 0;
}
gboolean
seaf_filelock_manager_is_file_locked (SeafFilelockManager *mgr,
const char *repo_id,
const char *path)
{
gboolean ret;
pthread_mutex_lock (&mgr->priv->hash_lock);
GHashTable *locks = g_hash_table_lookup (mgr->priv->repo_locked_files, repo_id);
if (!locks) {
pthread_mutex_unlock (&mgr->priv->hash_lock);
return FALSE;
}
LockInfo *info = g_hash_table_lookup (locks, path);
if (!info) {
pthread_mutex_unlock (&mgr->priv->hash_lock);
return FALSE;
}
ret = !info->locked_by_me;
pthread_mutex_unlock (&mgr->priv->hash_lock);
return ret;
}
gboolean
seaf_filelock_manager_is_file_locked_by_me (SeafFilelockManager *mgr,
const char *repo_id,
const char *path)
{
gboolean ret;
pthread_mutex_lock (&mgr->priv->hash_lock);
GHashTable *locks = g_hash_table_lookup (mgr->priv->repo_locked_files, repo_id);
if (!locks) {
pthread_mutex_unlock (&mgr->priv->hash_lock);
return FALSE;
}
LockInfo *info = g_hash_table_lookup (locks, path);
if (!info) {
pthread_mutex_unlock (&mgr->priv->hash_lock);
return FALSE;
}
ret = (info->locked_by_me > 0);
pthread_mutex_unlock (&mgr->priv->hash_lock);
return ret;
}
int
seaf_filelock_manager_get_lock_status (SeafFilelockManager *mgr,
const char *repo_id,
const char *path)
{
int ret;
pthread_mutex_lock (&mgr->priv->hash_lock);
GHashTable *locks = g_hash_table_lookup (mgr->priv->repo_locked_files, repo_id);
if (!locks) {
pthread_mutex_unlock (&mgr->priv->hash_lock);
return FILE_NOT_LOCKED;
}
LockInfo *info = g_hash_table_lookup (locks, path);
if (!info) {
pthread_mutex_unlock (&mgr->priv->hash_lock);
return FILE_NOT_LOCKED;
}
if (info->locked_by_me == LOCKED_MANUAL)
ret = FILE_LOCKED_BY_ME_MANUAL;
else if (info->locked_by_me == LOCKED_AUTO)
ret = FILE_LOCKED_BY_ME_AUTO;
else
ret = FILE_LOCKED_BY_OTHERS;
pthread_mutex_unlock (&mgr->priv->hash_lock);
return ret;
}
static void
update_in_memory (SeafFilelockManager *mgr, const char *repo_id, GHashTable *new_locks)
{
GHashTable *repo_hash = mgr->priv->repo_locked_files;
pthread_mutex_lock (&mgr->priv->hash_lock);
GHashTable *locks = g_hash_table_lookup (repo_hash, repo_id);
if (!locks) {
if (g_hash_table_size (new_locks) == 0) {
pthread_mutex_unlock (&mgr->priv->hash_lock);
return;
}
locks = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, (GDestroyNotify)lock_info_free);
g_hash_table_insert (repo_hash, g_strdup(repo_id), locks);
}
GHashTableIter iter;
gpointer key, value;
gpointer new_key, new_val;
char *path;
LockInfo *info;
gboolean exists;
int locked_by_me;
SeafRepo *repo;
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo) {
seaf_warning ("Failed to find repo %s\n", repo_id);
return;
}
g_hash_table_iter_init (&iter, locks);
while (g_hash_table_iter_next (&iter, &key, &value)) {
path = key;
info = value;
exists = g_hash_table_lookup_extended (new_locks, path, &new_key, &new_val);
if (!exists) {
g_hash_table_iter_remove (&iter);
} else {
locked_by_me = (int)(long)new_val;
if (!info->locked_by_me && locked_by_me) {
info->locked_by_me = locked_by_me;
} else if (info->locked_by_me && !locked_by_me) {
info->locked_by_me = locked_by_me;
}
}
}
g_hash_table_iter_init (&iter, new_locks);
while (g_hash_table_iter_next (&iter, &new_key, &new_val)) {
path = new_key;
locked_by_me = (int)(long)new_val;
if (!g_hash_table_lookup (locks, path)) {
info = g_new0 (LockInfo, 1);
info->locked_by_me = locked_by_me;
g_hash_table_insert (locks, g_strdup(path), info);
}
}
pthread_mutex_unlock (&mgr->priv->hash_lock);
seaf_repo_unref (repo);
}
static gint
compare_paths (gconstpointer a, gconstpointer b)
{
const char *patha = a, *pathb = b;
return strcmp (patha, pathb);
}
static int
update_db (SeafFilelockManager *mgr, const char *repo_id)
{
char *sql;
sqlite3_stmt *stmt;
GHashTable *locks;
GList *paths, *ptr;
char *path;
LockInfo *info;
pthread_mutex_lock (&mgr->priv->db_lock);
sql = "DELETE FROM ServerLockedFiles WHERE repo_id = ?";
stmt = sqlite_query_prepare (mgr->priv->db, sql);
sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT);
if (sqlite3_step (stmt) != SQLITE_DONE) {
seaf_warning ("Failed to remove server locked files for %.8s: %s.\n",
repo_id, sqlite3_errmsg (mgr->priv->db));
sqlite3_finalize (stmt);
pthread_mutex_unlock (&mgr->priv->db_lock);
return -1;
}
sqlite3_finalize (stmt);
locks = g_hash_table_lookup (mgr->priv->repo_locked_files, repo_id);
if (!locks || g_hash_table_size (locks) == 0) {
pthread_mutex_unlock (&mgr->priv->db_lock);
return 0;
}
paths = g_hash_table_get_keys (locks);
paths = g_list_sort (paths, compare_paths);
sql = "INSERT INTO ServerLockedFiles (repo_id, path, locked_by_me) VALUES (?, ?, ?)";
stmt = sqlite_query_prepare (mgr->priv->db, sql);
for (ptr = paths; ptr; ptr = ptr->next) {
path = ptr->data;
info = g_hash_table_lookup (locks, path);
sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT);
sqlite3_bind_text (stmt, 2, path, -1, SQLITE_TRANSIENT);
sqlite3_bind_int (stmt, 3, info->locked_by_me);
if (sqlite3_step (stmt) != SQLITE_DONE) {
seaf_warning ("Failed to insert server file lock for %.8s: %s.\n",
repo_id, sqlite3_errmsg (mgr->priv->db));
sqlite3_finalize (stmt);
pthread_mutex_unlock (&mgr->priv->db_lock);
return -1;
}
sqlite3_reset (stmt);
sqlite3_clear_bindings (stmt);
}
sqlite3_finalize (stmt);
g_list_free (paths);
pthread_mutex_unlock (&mgr->priv->db_lock);
return 0;
}
int
seaf_filelock_manager_update (SeafFilelockManager *mgr,
const char *repo_id,
GHashTable *new_locked_files)
{
update_in_memory (mgr, repo_id, new_locked_files);
int ret = update_db (mgr, repo_id);
return ret;
}
int
seaf_filelock_manager_update_timestamp (SeafFilelockManager *mgr,
const char *repo_id,
gint64 timestamp)
{
char sql[256];
int ret;
snprintf (sql, sizeof(sql),
"REPLACE INTO ServerLockedFilesTimestamp VALUES ('%s', %"G_GINT64_FORMAT")",
repo_id, timestamp);
pthread_mutex_lock (&mgr->priv->db_lock);
ret = sqlite_query_exec (mgr->priv->db, sql);
pthread_mutex_unlock (&mgr->priv->db_lock);
return ret;
}
gint64
seaf_filelock_manager_get_timestamp (SeafFilelockManager *mgr,
const char *repo_id)
{
char sql[256];
gint64 ret;
sqlite3_snprintf (sizeof(sql), sql,
"SELECT timestamp FROM ServerLockedFilesTimestamp WHERE repo_id = '%q'",
repo_id);
pthread_mutex_lock (&mgr->priv->db_lock);
ret = sqlite_get_int64 (mgr->priv->db, sql);
pthread_mutex_unlock (&mgr->priv->db_lock);
return ret;
}
int
seaf_filelock_manager_remove (SeafFilelockManager *mgr,
const char *repo_id)
{
char *sql;
sqlite3_stmt *stmt;
pthread_mutex_lock (&mgr->priv->db_lock);
sql = "DELETE FROM ServerLockedFiles WHERE repo_id = ?";
stmt = sqlite_query_prepare (mgr->priv->db, sql);
sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT);
if (sqlite3_step (stmt) != SQLITE_DONE) {
seaf_warning ("Failed to remove server locked files for %.8s: %s.\n",
repo_id, sqlite3_errmsg (mgr->priv->db));
sqlite3_finalize (stmt);
pthread_mutex_unlock (&mgr->priv->db_lock);
return -1;
}
sqlite3_finalize (stmt);
sql = "DELETE FROM ServerLockedFilesTimestamp WHERE repo_id = ?";
stmt = sqlite_query_prepare (mgr->priv->db, sql);
sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT);
if (sqlite3_step (stmt) != SQLITE_DONE) {
seaf_warning ("Failed to remove server locked files timestamp for %.8s: %s.\n",
repo_id, sqlite3_errmsg (mgr->priv->db));
sqlite3_finalize (stmt);
pthread_mutex_unlock (&mgr->priv->db_lock);
return -1;
}
sqlite3_finalize (stmt);
pthread_mutex_unlock (&mgr->priv->db_lock);
pthread_mutex_lock (&mgr->priv->hash_lock);
g_hash_table_remove (mgr->priv->repo_locked_files, repo_id);
pthread_mutex_unlock (&mgr->priv->hash_lock);
return 0;
}
static int
mark_file_locked_in_db (SeafFilelockManager *mgr,
const char *repo_id,
const char *path,
int locked_by_me)
{
char *sql;
sqlite3_stmt *stmt;
pthread_mutex_lock (&mgr->priv->db_lock);
sql = "REPLACE INTO ServerLockedFiles (repo_id, path, locked_by_me) VALUES (?, ?, ?)";
stmt = sqlite_query_prepare (mgr->priv->db, sql);
sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT);
sqlite3_bind_text (stmt, 2, path, -1, SQLITE_TRANSIENT);
sqlite3_bind_int (stmt, 3, locked_by_me);
if (sqlite3_step (stmt) != SQLITE_DONE) {
seaf_warning ("Failed to update server locked files for %.8s: %s.\n",
repo_id, sqlite3_errmsg (mgr->priv->db));
sqlite3_finalize (stmt);
pthread_mutex_unlock (&mgr->priv->db_lock);
return -1;
}
sqlite3_finalize (stmt);
pthread_mutex_unlock (&mgr->priv->db_lock);
return 0;
}
int
seaf_filelock_manager_mark_file_locked (SeafFilelockManager *mgr,
const char *repo_id,
const char *path,
FileLockType type)
{
GHashTable *locks;
LockInfo *info;
pthread_mutex_lock (&mgr->priv->hash_lock);
locks = g_hash_table_lookup (mgr->priv->repo_locked_files, repo_id);
if (!locks) {
locks = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, (GDestroyNotify)lock_info_free);
g_hash_table_insert (mgr->priv->repo_locked_files,
g_strdup(repo_id), locks);
}
info = g_hash_table_lookup (locks, path);
if (!info) {
info = g_new0 (LockInfo, 1);
g_hash_table_insert (locks, g_strdup(path), info);
}
info->locked_by_me = type;
pthread_mutex_unlock (&mgr->priv->hash_lock);
return mark_file_locked_in_db (mgr, repo_id, path, info->locked_by_me);
}
static int
remove_locked_file_from_db (SeafFilelockManager *mgr,
const char *repo_id,
const char *path)
{
char *sql;
sqlite3_stmt *stmt;
pthread_mutex_lock (&mgr->priv->db_lock);
sql = "DELETE FROM ServerLockedFiles WHERE repo_id = ? AND path = ?";
stmt = sqlite_query_prepare (mgr->priv->db, sql);
sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT);
sqlite3_bind_text (stmt, 2, path, -1, SQLITE_TRANSIENT);
if (sqlite3_step (stmt) != SQLITE_DONE) {
seaf_warning ("Failed to remove locked file %s from %.8s: %s.\n",
path, repo_id, sqlite3_errmsg (mgr->priv->db));
sqlite3_finalize (stmt);
pthread_mutex_unlock (&mgr->priv->db_lock);
return -1;
}
sqlite3_finalize (stmt);
pthread_mutex_unlock (&mgr->priv->db_lock);
return 0;
}
int
seaf_filelock_manager_mark_file_unlocked (SeafFilelockManager *mgr,
const char *repo_id,
const char *path)
{
GHashTable *locks;
pthread_mutex_lock (&mgr->priv->hash_lock);
locks = g_hash_table_lookup (mgr->priv->repo_locked_files, repo_id);
if (!locks) {
pthread_mutex_unlock (&mgr->priv->hash_lock);
return 0;
}
g_hash_table_remove (locks, path);
pthread_mutex_unlock (&mgr->priv->hash_lock);
return remove_locked_file_from_db (mgr, repo_id, path);
}
int
seaf_filelock_manager_lock_file (SeafFilelockManager *mgr,
const char *repo_id,
const char *path,
FileLockType type)
{
int ret = -1;
ret = seaf_filelock_manager_mark_file_locked (seaf->filelock_mgr, repo_id, path, type);
if (ret < 0) {
return ret;
}
return 0;
}
int
seaf_filelock_manager_unlock_file (SeafFilelockManager *mgr,
const char *repo_id,
const char *path)
{
int ret = -1;
ret = seaf_filelock_manager_mark_file_unlocked (mgr, repo_id, path);
if (ret < 0) {
return ret;
}
return 0;
}
seadrive-fuse-3.0.13/src/filelock-mgr.h 0000664 0000000 0000000 00000005634 14761776747 0017715 0 ustar 00root root 0000000 0000000 #ifndef SEAF_FILELOCK_MGR_H
#define SEAF_FILELOCK_MGR_H
#include
struct _SeafileSession;
struct _FilelockMgrPriv;
struct _SeafFilelockManager {
struct _SeafileSession *session;
struct _FilelockMgrPriv *priv;
};
typedef struct _SeafFilelockManager SeafFilelockManager;
struct _SeafFilelockManager *
seaf_filelock_manager_new (struct _SeafileSession *session);
int
seaf_filelock_manager_init (SeafFilelockManager *mgr);
int
seaf_filelock_manager_start (SeafFilelockManager *mgr);
gboolean
seaf_filelock_manager_is_file_locked (SeafFilelockManager *mgr,
const char *repo_id,
const char *path);
typedef enum FileLockStatus {
FILE_NOT_LOCKED = 0,
FILE_LOCKED_BY_OTHERS,
FILE_LOCKED_BY_ME_MANUAL,
FILE_LOCKED_BY_ME_AUTO,
} FileLockStatus;
/* When a file is locked by me, it can have two reasons:
* - Locked by the user manually
* - Auto-Locked by Seafile when it detects Office opens the file.
*/
typedef enum FileLockType {
LOCKED_OTHERS = 0,
LOCKED_MANUAL,
LOCKED_AUTO,
} FileLockType;
int
seaf_filelock_manager_get_lock_status (SeafFilelockManager *mgr,
const char *repo_id,
const char *path);
gboolean
seaf_filelock_manager_is_file_locked_by_me (SeafFilelockManager *mgr,
const char *repo_id,
const char *path);
int
seaf_filelock_manager_update (SeafFilelockManager *mgr,
const char *repo_id,
GHashTable *new_locked_files);
int
seaf_filelock_manager_update_timestamp (SeafFilelockManager *mgr,
const char *repo_id,
gint64 timestamp);
gint64
seaf_filelock_manager_get_timestamp (SeafFilelockManager *mgr,
const char *repo_id);
int
seaf_filelock_manager_remove (SeafFilelockManager *mgr,
const char *repo_id);
int
seaf_filelock_manager_mark_file_locked (SeafFilelockManager *mgr,
const char *repo_id,
const char *path,
FileLockType type);
int
seaf_filelock_manager_mark_file_unlocked (SeafFilelockManager *mgr,
const char *repo_id,
const char *path);
int
seaf_filelock_manager_lock_file (SeafFilelockManager *mgr,
const char *repo_id,
const char *path,
FileLockType type);
int
seaf_filelock_manager_unlock_file (SeafFilelockManager *mgr,
const char *repo_id,
const char *path);
#endif
seadrive-fuse-3.0.13/src/fs-mgr.c 0000664 0000000 0000000 00000173035 14761776747 0016531 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#include "common.h"
#include
#include
#include
#include "seafile-session.h"
#include "seafile-error.h"
#include "fs-mgr.h"
#include "block-mgr.h"
#include "utils.h"
#define DEBUG_FLAG SEAFILE_DEBUG_OTHER
#include "log.h"
#include "db.h"
#define SEAF_TMP_EXT "~"
struct _SeafFSManagerPriv {
/* GHashTable *seafile_cache; */
GHashTable *bl_cache;
};
typedef struct SeafileOndisk {
guint32 type;
guint64 file_size;
unsigned char block_ids[0];
} SeafileOndisk;
typedef struct DirentOndisk {
guint32 mode;
char id[40];
guint32 name_len;
char name[0];
} DirentOndisk;
typedef struct SeafdirOndisk {
guint32 type;
char dirents[0];
} SeafdirOndisk;
uint32_t
calculate_chunk_size (uint64_t total_size);
static int
write_seafile (SeafFSManager *fs_mgr,
const char *repo_id, int version,
CDCFileDescriptor *cdc,
unsigned char *obj_sha1);
SeafFSManager *
seaf_fs_manager_new (SeafileSession *seaf,
const char *seaf_dir)
{
SeafFSManager *mgr = g_new0 (SeafFSManager, 1);
mgr->seaf = seaf;
mgr->obj_store = seaf_obj_store_new (seaf, "fs");
if (!mgr->obj_store) {
g_free (mgr);
return NULL;
}
mgr->priv = g_new0(SeafFSManagerPriv, 1);
return mgr;
}
int
seaf_fs_manager_init (SeafFSManager *mgr)
{
if (seaf_obj_store_init (mgr->obj_store) < 0) {
seaf_warning ("[fs mgr] Failed to init fs object store.\n");
return -1;
}
return 0;
}
static void *
create_seafile_v0 (CDCFileDescriptor *cdc, int *ondisk_size, char *seafile_id)
{
SeafileOndisk *ondisk;
rawdata_to_hex (cdc->file_sum, seafile_id, 20);
*ondisk_size = sizeof(SeafileOndisk) + cdc->block_nr * 20;
ondisk = (SeafileOndisk *)g_new0 (char, *ondisk_size);
ondisk->type = htonl(SEAF_METADATA_TYPE_FILE);
ondisk->file_size = hton64 (cdc->file_size);
memcpy (ondisk->block_ids, cdc->blk_sha1s, cdc->block_nr * 20);
return ondisk;
}
static void *
create_seafile_json (int repo_version,
CDCFileDescriptor *cdc,
int *ondisk_size,
char *seafile_id)
{
json_t *object, *block_id_array;
object = json_object ();
json_object_set_int_member (object, "type", SEAF_METADATA_TYPE_FILE);
json_object_set_int_member (object, "version",
seafile_version_from_repo_version(repo_version));
json_object_set_int_member (object, "size", cdc->file_size);
block_id_array = json_array ();
int i;
uint8_t *ptr = cdc->blk_sha1s;
char block_id[41];
for (i = 0; i < cdc->block_nr; ++i) {
rawdata_to_hex (ptr, block_id, 20);
json_array_append_new (block_id_array, json_string(block_id));
ptr += 20;
}
json_object_set_new (object, "block_ids", block_id_array);
char *data = json_dumps (object, JSON_SORT_KEYS);
*ondisk_size = strlen(data);
/* The seafile object id is sha1 hash of the json object. */
unsigned char sha1[20];
calculate_sha1 (sha1, data, *ondisk_size);
rawdata_to_hex (sha1, seafile_id, 20);
json_decref (object);
return data;
}
void
seaf_fs_manager_calculate_seafile_id_json (int repo_version,
CDCFileDescriptor *cdc,
guint8 *file_id_sha1)
{
json_t *object, *block_id_array;
object = json_object ();
json_object_set_int_member (object, "type", SEAF_METADATA_TYPE_FILE);
json_object_set_int_member (object, "version",
seafile_version_from_repo_version(repo_version));
json_object_set_int_member (object, "size", cdc->file_size);
block_id_array = json_array ();
int i;
uint8_t *ptr = cdc->blk_sha1s;
char block_id[41];
for (i = 0; i < cdc->block_nr; ++i) {
rawdata_to_hex (ptr, block_id, 20);
json_array_append_new (block_id_array, json_string(block_id));
ptr += 20;
}
json_object_set_new (object, "block_ids", block_id_array);
char *data = json_dumps (object, JSON_SORT_KEYS);
int ondisk_size = strlen(data);
/* The seafile object id is sha1 hash of the json object. */
calculate_sha1 (file_id_sha1, data, ondisk_size);
json_decref (object);
free (data);
}
static int
write_seafile (SeafFSManager *fs_mgr,
const char *repo_id,
int version,
CDCFileDescriptor *cdc,
unsigned char sha1[])
{
int ret = 0;
char seafile_id[41];
void *ondisk;
int ondisk_size;
if (version > 0) {
ondisk = create_seafile_json (version, cdc, &ondisk_size, seafile_id);
guint8 *compressed;
int outlen;
if (seaf_compress (ondisk, ondisk_size, &compressed, &outlen) < 0) {
seaf_warning ("Failed to compress seafile obj %s:%s.\n",
repo_id, seafile_id);
ret = -1;
free (ondisk);
goto out;
}
if (seaf_obj_store_write_obj (fs_mgr->obj_store, repo_id, version, seafile_id,
compressed, outlen, FALSE) < 0)
ret = -1;
g_free (compressed);
free (ondisk);
} else {
ondisk = create_seafile_v0 (cdc, &ondisk_size, seafile_id);
if (seaf_obj_store_write_obj (fs_mgr->obj_store, repo_id, version, seafile_id,
ondisk, ondisk_size, FALSE) < 0)
ret = -1;
g_free (ondisk);
}
out:
if (ret == 0)
hex_to_rawdata (seafile_id, sha1, 20);
return ret;
}
uint32_t
calculate_chunk_size (uint64_t total_size)
{
const uint64_t GiB = 1073741824;
const uint64_t MiB = 1048576;
if (total_size >= (8 * GiB)) return 8 * MiB;
if (total_size >= (4 * GiB)) return 4 * MiB;
if (total_size >= (2 * GiB)) return 2 * MiB;
return 1 * MiB;
}
static int
do_write_chunk (const char *repo_id, int version,
uint8_t *checksum, const char *buf, int len)
{
SeafBlockManager *blk_mgr = seaf->block_mgr;
char chksum_str[41];
BlockHandle *handle;
int n;
rawdata_to_hex (checksum, chksum_str, 20);
/* Don't write if the block already exists. */
if (seaf_block_manager_block_exists (seaf->block_mgr,
repo_id, version,
chksum_str))
return 0;
handle = seaf_block_manager_open_block (blk_mgr,
repo_id, version,
chksum_str, BLOCK_WRITE);
if (!handle) {
seaf_warning ("Failed to open block %s.\n", chksum_str);
return -1;
}
n = seaf_block_manager_write_block (blk_mgr, handle, buf, len);
if (n < 0) {
seaf_warning ("Failed to write chunk %s.\n", chksum_str);
seaf_block_manager_close_block (blk_mgr, handle);
seaf_block_manager_block_handle_free (blk_mgr, handle);
return -1;
}
if (seaf_block_manager_close_block (blk_mgr, handle) < 0) {
seaf_warning ("failed to close block %s.\n", chksum_str);
seaf_block_manager_block_handle_free (blk_mgr, handle);
return -1;
}
if (seaf_block_manager_commit_block (blk_mgr, handle) < 0) {
seaf_warning ("failed to commit chunk %s.\n", chksum_str);
seaf_block_manager_block_handle_free (blk_mgr, handle);
return -1;
}
seaf_block_manager_block_handle_free (blk_mgr, handle);
return 0;
}
/* write the chunk and store its checksum */
int
seafile_write_chunk (const char *repo_id,
int version,
CDCDescriptor *chunk,
SeafileCrypt *crypt,
uint8_t *checksum,
gboolean write_data)
{
GChecksum *ctx = g_checksum_new (G_CHECKSUM_SHA1);
gsize len = 20;
int ret = 0;
/* Encrypt before write to disk if needed, and we don't encrypt
* empty files. */
if (crypt != NULL && chunk->len) {
char *encrypted_buf = NULL; /* encrypted output */
int enc_len = -1; /* encrypted length */
ret = seafile_encrypt (&encrypted_buf, /* output */
&enc_len, /* output len */
chunk->block_buf, /* input */
chunk->len, /* input len */
crypt);
if (ret != 0) {
seaf_warning ("Error: failed to encrypt block\n");
g_checksum_free (ctx);
return -1;
}
g_checksum_update (ctx, (unsigned char *)encrypted_buf, enc_len);
g_checksum_get_digest (ctx, checksum, &len);
if (write_data)
ret = do_write_chunk (repo_id, version, checksum, encrypted_buf, enc_len);
g_free (encrypted_buf);
} else {
/* not a encrypted repo, go ahead */
g_checksum_update (ctx, (unsigned char *)chunk->block_buf, chunk->len);
g_checksum_get_digest (ctx, checksum, &len);
if (write_data)
ret = do_write_chunk (repo_id, version, checksum, chunk->block_buf, chunk->len);
}
g_checksum_free (ctx);
return ret;
}
static void
create_cdc_for_empty_file (CDCFileDescriptor *cdc)
{
memset (cdc, 0, sizeof(CDCFileDescriptor));
}
int
seaf_fs_manager_index_blocks (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *file_path,
SeafileCrypt *crypt,
gboolean write_data,
unsigned char sha1[])
{
SeafStat sb;
CDCFileDescriptor cdc;
if (seaf_stat (file_path, &sb) < 0) {
seaf_warning ("Bad file %s: %s.\n", file_path, strerror(errno));
return -1;
}
g_return_val_if_fail (S_ISREG(sb.st_mode), -1);
if (sb.st_size == 0) {
/* handle empty file. */
memset (sha1, 0, 20);
create_cdc_for_empty_file (&cdc);
} else {
memset (&cdc, 0, sizeof(cdc));
cdc.block_sz = CDC_AVERAGE_BLOCK_SIZE;
cdc.block_min_sz = CDC_MIN_BLOCK_SIZE;
cdc.block_max_sz = CDC_MAX_BLOCK_SIZE;
cdc.write_block = seafile_write_chunk;
memcpy (cdc.repo_id, repo_id, 36);
cdc.version = version;
if (filename_chunk_cdc (file_path, &cdc, crypt, write_data) < 0) {
seaf_warning ("Failed to chunk file with CDC.\n");
return -1;
}
if (write_data && write_seafile (mgr, repo_id, version, &cdc, sha1) < 0) {
g_free (cdc.blk_sha1s);
seaf_warning ("Failed to write seafile for %s.\n", file_path);
return -1;
}
}
if (cdc.blk_sha1s)
free (cdc.blk_sha1s);
return 0;
}
void
seafile_ref (Seafile *seafile)
{
++seafile->ref_count;
}
static void
seafile_free (Seafile *seafile)
{
int i;
if (seafile->blk_sha1s) {
for (i = 0; i < seafile->n_blocks; ++i)
g_free (seafile->blk_sha1s[i]);
g_free (seafile->blk_sha1s);
}
g_free (seafile);
}
void
seafile_unref (Seafile *seafile)
{
if (!seafile)
return;
if (--seafile->ref_count <= 0)
seafile_free (seafile);
}
static Seafile *
seafile_from_v0_data (const char *id, const void *data, int len)
{
const SeafileOndisk *ondisk = data;
Seafile *seafile;
int id_list_len, n_blocks;
if (len < sizeof(SeafileOndisk)) {
seaf_warning ("[fs mgr] Corrupt seafile object %s.\n", id);
return NULL;
}
if (ntohl(ondisk->type) != SEAF_METADATA_TYPE_FILE) {
seaf_warning ("[fd mgr] %s is not a file.\n", id);
return NULL;
}
id_list_len = len - sizeof(SeafileOndisk);
if (id_list_len % 20 != 0) {
seaf_warning ("[fs mgr] Corrupt seafile object %s.\n", id);
return NULL;
}
n_blocks = id_list_len / 20;
seafile = g_new0 (Seafile, 1);
seafile->object.type = SEAF_METADATA_TYPE_FILE;
seafile->version = 0;
memcpy (seafile->file_id, id, 41);
seafile->file_size = ntoh64 (ondisk->file_size);
seafile->n_blocks = n_blocks;
seafile->blk_sha1s = g_new0 (char*, seafile->n_blocks);
const unsigned char *blk_sha1_ptr = ondisk->block_ids;
int i;
for (i = 0; i < seafile->n_blocks; ++i) {
char *blk_sha1 = g_new0 (char, 41);
seafile->blk_sha1s[i] = blk_sha1;
rawdata_to_hex (blk_sha1_ptr, blk_sha1, 20);
blk_sha1_ptr += 20;
}
seafile->ref_count = 1;
return seafile;
}
static Seafile *
seafile_from_json_object (const char *id, json_t *object)
{
json_t *block_id_array = NULL;
int type;
int version;
guint64 file_size;
Seafile *seafile = NULL;
/* Sanity checks. */
type = json_object_get_int_member (object, "type");
if (type != SEAF_METADATA_TYPE_FILE) {
seaf_debug ("Object %s is not a file.\n", id);
return NULL;
}
version = (int) json_object_get_int_member (object, "version");
if (version < 1) {
seaf_debug ("Seafile object %s version should be > 0, version is %d.\n",
id, version);
return NULL;
}
file_size = (guint64) json_object_get_int_member (object, "size");
block_id_array = json_object_get (object, "block_ids");
if (!block_id_array) {
seaf_debug ("No block id array in seafile object %s.\n", id);
return NULL;
}
seafile = g_new0 (Seafile, 1);
seafile->object.type = SEAF_METADATA_TYPE_FILE;
memcpy (seafile->file_id, id, 40);
seafile->version = version;
seafile->file_size = file_size;
seafile->n_blocks = json_array_size (block_id_array);
seafile->blk_sha1s = g_new0 (char *, seafile->n_blocks);
int i;
json_t *block_id_obj;
const char *block_id;
for (i = 0; i < seafile->n_blocks; ++i) {
block_id_obj = json_array_get (block_id_array, i);
block_id = json_string_value (block_id_obj);
if (!block_id || !is_object_id_valid(block_id)) {
seafile_free (seafile);
return NULL;
}
seafile->blk_sha1s[i] = g_strdup(block_id);
}
seafile->ref_count = 1;
return seafile;
}
static Seafile *
seafile_from_json (const char *id, void *data, int len)
{
guint8 *decompressed;
int outlen;
json_t *object = NULL;
json_error_t error;
Seafile *seafile;
if (seaf_decompress (data, len, &decompressed, &outlen) < 0) {
seaf_warning ("Failed to decompress seafile object %s.\n", id);
return NULL;
}
object = json_loadb ((const char *)decompressed, outlen, 0, &error);
g_free (decompressed);
if (!object) {
seaf_warning ("Failed to load seafile json object: %s.\n", error.text);
return NULL;
}
seafile = seafile_from_json_object (id, object);
json_decref (object);
return seafile;
}
static Seafile *
seafile_from_data (const char *id, void *data, int len, gboolean is_json)
{
if (is_json)
return seafile_from_json (id, data, len);
else
return seafile_from_v0_data (id, data, len);
}
Seafile *
seaf_fs_manager_get_seafile (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *file_id)
{
void *data;
int len;
Seafile *seafile;
#if 0
seafile = g_hash_table_lookup (mgr->priv->seafile_cache, file_id);
if (seafile) {
seafile_ref (seafile);
return seafile;
}
#endif
if (memcmp (file_id, EMPTY_SHA1, 40) == 0) {
seafile = g_new0 (Seafile, 1);
memset (seafile->file_id, '0', 40);
seafile->ref_count = 1;
return seafile;
}
if (seaf_obj_store_read_obj (mgr->obj_store, repo_id, version,
file_id, &data, &len) < 0) {
seaf_warning ("[fs mgr] Failed to read file %s.\n", file_id);
return NULL;
}
seafile = seafile_from_data (file_id, data, len, (version > 0));
g_free (data);
return seafile;
}
static guint8 *
seafile_to_v0_data (Seafile *file, int *len)
{
SeafileOndisk *ondisk;
*len = sizeof(SeafileOndisk) + file->n_blocks * 20;
ondisk = (SeafileOndisk *)g_new0 (char, *len);
ondisk->type = htonl(SEAF_METADATA_TYPE_FILE);
ondisk->file_size = hton64 (file->file_size);
guint8 *ptr = ondisk->block_ids;
int i;
for (i = 0; i < file->n_blocks; ++i) {
hex_to_rawdata (file->blk_sha1s[i], ptr, 20);
ptr += 20;
}
return (guint8 *)ondisk;
}
static guint8 *
seafile_to_json (Seafile *file, int *len)
{
json_t *object, *block_id_array;
object = json_object ();
json_object_set_int_member (object, "type", SEAF_METADATA_TYPE_FILE);
json_object_set_int_member (object, "version", file->version);
json_object_set_int_member (object, "size", file->file_size);
block_id_array = json_array ();
int i;
for (i = 0; i < file->n_blocks; ++i) {
json_array_append_new (block_id_array, json_string(file->blk_sha1s[i]));
}
json_object_set_new (object, "block_ids", block_id_array);
char *data = json_dumps (object, JSON_SORT_KEYS);
*len = strlen(data);
unsigned char sha1[20];
calculate_sha1 (sha1, data, *len);
rawdata_to_hex (sha1, file->file_id, 20);
json_decref (object);
return (guint8 *)data;
}
static guint8 *
seafile_to_data (Seafile *file, int *len)
{
if (file->version > 0) {
guint8 *data;
int orig_len;
guint8 *compressed;
data = seafile_to_json (file, &orig_len);
if (!data)
return NULL;
if (seaf_compress (data, orig_len, &compressed, len) < 0) {
seaf_warning ("Failed to compress file object %s.\n", file->file_id);
g_free (data);
return NULL;
}
g_free (data);
return compressed;
} else
return seafile_to_v0_data (file, len);
}
int
seafile_save (SeafFSManager *fs_mgr,
const char *repo_id,
int version,
Seafile *file)
{
guint8 *data;
int len;
int ret = 0;
data = seafile_to_data (file, &len);
if (!data)
return -1;
if (seaf_obj_store_write_obj (fs_mgr->obj_store, repo_id, version, file->file_id,
data, len, FALSE) < 0)
ret = -1;
g_free (data);
return ret;
}
static void compute_dir_id_v0 (SeafDir *dir, GList *entries)
{
GChecksum *ctx;
GList *p;
uint8_t sha1[20];
gsize len = 20;
SeafDirent *dent;
guint32 mode_le;
/* ID for empty dirs is EMPTY_SHA1. */
if (entries == NULL) {
memset (dir->dir_id, '0', 40);
return;
}
ctx = g_checksum_new (G_CHECKSUM_SHA1);
for (p = entries; p; p = p->next) {
dent = (SeafDirent *)p->data;
g_checksum_update (ctx, (unsigned char *)dent->id, 40);
g_checksum_update (ctx, (unsigned char *)dent->name, dent->name_len);
/* Convert mode to little endian before compute. */
if (G_BYTE_ORDER == G_BIG_ENDIAN)
mode_le = GUINT32_SWAP_LE_BE (dent->mode);
else
mode_le = dent->mode;
g_checksum_update (ctx, (unsigned char *)&mode_le, sizeof(mode_le));
}
g_checksum_get_digest (ctx, sha1, &len);
rawdata_to_hex (sha1, dir->dir_id, 20);
g_checksum_free (ctx);
}
SeafDir *
seaf_dir_new (const char *id, GList *entries, int version)
{
SeafDir *dir;
dir = g_new0(SeafDir, 1);
dir->version = version;
if (id != NULL) {
memcpy(dir->dir_id, id, 40);
dir->dir_id[40] = '\0';
} else if (version == 0) {
compute_dir_id_v0 (dir, entries);
}
dir->entries = entries;
if (dir->entries != NULL)
dir->ondisk = seaf_dir_to_data (dir, &dir->ondisk_size);
else
memcpy (dir->dir_id, EMPTY_SHA1, 40);
return dir;
}
void
seaf_dir_free (SeafDir *dir)
{
if (dir == NULL)
return;
GList *ptr = dir->entries;
while (ptr) {
seaf_dirent_free ((SeafDirent *)ptr->data);
ptr = ptr->next;
}
g_list_free (dir->entries);
g_free (dir->ondisk);
g_free(dir);
}
SeafDirent *
seaf_dirent_new (int version, const char *sha1, int mode, const char *name,
gint64 mtime, const char *modifier, gint64 size)
{
SeafDirent *dent;
dent = g_new0 (SeafDirent, 1);
dent->version = version;
memcpy(dent->id, sha1, 40);
dent->id[40] = '\0';
/* Mode for files must have 0644 set. To prevent the caller from forgetting,
* we set the bits here.
*/
if (S_ISREG(mode))
dent->mode = (mode | 0644);
else
dent->mode = mode;
dent->name = g_strdup(name);
dent->name_len = strlen(name);
if (version > 0) {
dent->mtime = mtime;
if (S_ISREG(mode)) {
dent->modifier = g_strdup(modifier);
dent->size = size;
}
}
return dent;
}
void
seaf_dirent_free (SeafDirent *dent)
{
if (!dent)
return;
g_free (dent->name);
g_free (dent->modifier);
g_free (dent);
}
SeafDirent *
seaf_dirent_dup (SeafDirent *dent)
{
SeafDirent *new_dent;
new_dent = g_memdup (dent, sizeof(SeafDirent));
new_dent->name = g_strdup(dent->name);
new_dent->modifier = g_strdup(dent->modifier);
return new_dent;
}
static SeafDir *
seaf_dir_from_v0_data (const char *dir_id, const uint8_t *data, int len)
{
SeafDir *root;
SeafDirent *dent;
const uint8_t *ptr;
int remain;
int dirent_base_size;
guint32 meta_type;
guint32 name_len;
ptr = data;
remain = len;
meta_type = get32bit (&ptr);
remain -= 4;
if (meta_type != SEAF_METADATA_TYPE_DIR) {
seaf_warning ("Data does not contain a directory.\n");
return NULL;
}
root = g_new0(SeafDir, 1);
root->object.type = SEAF_METADATA_TYPE_DIR;
root->version = 0;
memcpy(root->dir_id, dir_id, 40);
root->dir_id[40] = '\0';
dirent_base_size = 2 * sizeof(guint32) + 40;
while (remain > dirent_base_size) {
dent = g_new0(SeafDirent, 1);
dent->version = 0;
dent->mode = get32bit (&ptr);
memcpy (dent->id, ptr, 40);
dent->id[40] = '\0';
ptr += 40;
name_len = get32bit (&ptr);
remain -= dirent_base_size;
if (remain >= name_len) {
dent->name_len = MIN (name_len, SEAF_DIR_NAME_LEN - 1);
dent->name = g_strndup((const char *)ptr, dent->name_len);
ptr += dent->name_len;
remain -= dent->name_len;
} else {
seaf_warning ("Bad data format for dir objcet %s.\n", dir_id);
g_free (dent);
goto bad;
}
root->entries = g_list_prepend (root->entries, dent);
}
root->entries = g_list_reverse (root->entries);
return root;
bad:
seaf_dir_free (root);
return NULL;
}
static SeafDirent *
parse_dirent (const char *dir_id, int version, json_t *object)
{
guint32 mode;
const char *id;
const char *name;
gint64 mtime;
const char *modifier;
gint64 size;
mode = (guint32) json_object_get_int_member (object, "mode");
id = json_object_get_string_member (object, "id");
if (!id) {
seaf_debug ("Dirent id not set for dir object %s.\n", dir_id);
return NULL;
}
if (!is_object_id_valid (id)) {
seaf_debug ("Dirent id is invalid for dir object %s.\n", dir_id);
return NULL;
}
name = json_object_get_string_member (object, "name");
if (!name) {
seaf_debug ("Dirent name not set for dir object %s.\n", dir_id);
return NULL;
}
mtime = json_object_get_int_member (object, "mtime");
if (S_ISREG(mode)) {
modifier = json_object_get_string_member (object, "modifier");
if (!modifier) {
seaf_debug ("Dirent modifier not set for dir object %s.\n", dir_id);
return NULL;
}
size = json_object_get_int_member (object, "size");
}
SeafDirent *dirent = g_new0 (SeafDirent, 1);
dirent->version = version;
dirent->mode = mode;
memcpy (dirent->id, id, 40);
dirent->name_len = strlen(name);
dirent->name = g_strdup(name);
dirent->mtime = mtime;
if (S_ISREG(mode)) {
dirent->modifier = g_strdup(modifier);
dirent->size = size;
}
return dirent;
}
static SeafDir *
seaf_dir_from_json_object (const char *dir_id, json_t *object)
{
json_t *dirent_array = NULL;
int type;
int version;
SeafDir *dir = NULL;
/* Sanity checks. */
type = json_object_get_int_member (object, "type");
if (type != SEAF_METADATA_TYPE_DIR) {
seaf_debug ("Object %s is not a dir.\n", dir_id);
return NULL;
}
version = (int) json_object_get_int_member (object, "version");
if (version < 1) {
seaf_debug ("Dir object %s version should be > 0, version is %d.\n",
dir_id, version);
return NULL;
}
dirent_array = json_object_get (object, "dirents");
if (!dirent_array) {
seaf_debug ("No dirents in dir object %s.\n", dir_id);
return NULL;
}
dir = g_new0 (SeafDir, 1);
dir->object.type = SEAF_METADATA_TYPE_DIR;
memcpy (dir->dir_id, dir_id, 40);
dir->version = version;
size_t n_dirents = json_array_size (dirent_array);
int i;
json_t *dirent_obj;
SeafDirent *dirent;
for (i = 0; i < n_dirents; ++i) {
dirent_obj = json_array_get (dirent_array, i);
dirent = parse_dirent (dir_id, version, dirent_obj);
if (!dirent) {
seaf_dir_free (dir);
return NULL;
}
dir->entries = g_list_prepend (dir->entries, dirent);
}
dir->entries = g_list_reverse (dir->entries);
return dir;
}
static SeafDir *
seaf_dir_from_json (const char *dir_id, uint8_t *data, int len)
{
guint8 *decompressed;
int outlen;
json_t *object = NULL;
json_error_t error;
SeafDir *dir;
if (seaf_decompress (data, len, &decompressed, &outlen) < 0) {
seaf_warning ("Failed to decompress dir object %s.\n", dir_id);
return NULL;
}
object = json_loadb ((const char *)decompressed, outlen, 0, &error);
g_free (decompressed);
if (!object) {
seaf_warning ("Failed to load seafdir json object: %s.\n", error.text);
return NULL;
}
dir = seaf_dir_from_json_object (dir_id, object);
json_decref (object);
return dir;
}
SeafDir *
seaf_dir_from_data (const char *dir_id, uint8_t *data, int len,
gboolean is_json)
{
if (is_json)
return seaf_dir_from_json (dir_id, data, len);
else
return seaf_dir_from_v0_data (dir_id, data, len);
}
inline static int
ondisk_dirent_size (SeafDirent *dirent)
{
return sizeof(DirentOndisk) + dirent->name_len;
}
static void *
seaf_dir_to_v0_data (SeafDir *dir, int *len)
{
SeafdirOndisk *ondisk;
int dir_ondisk_size = sizeof(SeafdirOndisk);
GList *dirents = dir->entries;
GList *ptr;
SeafDirent *de;
char *p;
DirentOndisk *de_ondisk;
for (ptr = dirents; ptr; ptr = ptr->next) {
de = ptr->data;
dir_ondisk_size += ondisk_dirent_size (de);
}
*len = dir_ondisk_size;
ondisk = (SeafdirOndisk *) g_new0 (char, dir_ondisk_size);
ondisk->type = htonl (SEAF_METADATA_TYPE_DIR);
p = ondisk->dirents;
for (ptr = dirents; ptr; ptr = ptr->next) {
de = ptr->data;
de_ondisk = (DirentOndisk *) p;
de_ondisk->mode = htonl(de->mode);
memcpy (de_ondisk->id, de->id, 40);
de_ondisk->name_len = htonl (de->name_len);
memcpy (de_ondisk->name, de->name, de->name_len);
p += ondisk_dirent_size (de);
}
return (void *)ondisk;
}
static void
add_to_dirent_array (json_t *array, SeafDirent *dirent)
{
json_t *object;
object = json_object ();
json_object_set_int_member (object, "mode", dirent->mode);
json_object_set_string_member (object, "id", dirent->id);
json_object_set_string_member (object, "name", dirent->name);
json_object_set_int_member (object, "mtime", dirent->mtime);
if (S_ISREG(dirent->mode)) {
json_object_set_string_member (object, "modifier", dirent->modifier);
json_object_set_int_member (object, "size", dirent->size);
}
json_array_append_new (array, object);
}
static void *
seaf_dir_to_json (SeafDir *dir, int *len)
{
json_t *object, *dirent_array;
GList *ptr;
SeafDirent *dirent;
object = json_object ();
json_object_set_int_member (object, "type", SEAF_METADATA_TYPE_DIR);
json_object_set_int_member (object, "version", dir->version);
dirent_array = json_array ();
for (ptr = dir->entries; ptr; ptr = ptr->next) {
dirent = ptr->data;
add_to_dirent_array (dirent_array, dirent);
}
json_object_set_new (object, "dirents", dirent_array);
char *data = json_dumps (object, JSON_SORT_KEYS);
*len = strlen(data);
/* The dir object id is sha1 hash of the json object. */
unsigned char sha1[20];
calculate_sha1 (sha1, data, *len);
rawdata_to_hex (sha1, dir->dir_id, 20);
json_decref (object);
return data;
}
void *
seaf_dir_to_data (SeafDir *dir, int *len)
{
if (dir->version > 0) {
guint8 *data;
int orig_len;
guint8 *compressed;
data = seaf_dir_to_json (dir, &orig_len);
if (!data)
return NULL;
if (seaf_compress (data, orig_len, &compressed, len) < 0) {
seaf_warning ("Failed to compress dir object %s.\n", dir->dir_id);
g_free (data);
return NULL;
}
g_free (data);
return compressed;
} else
return seaf_dir_to_v0_data (dir, len);
}
int
seaf_dir_save (SeafFSManager *fs_mgr,
const char *repo_id,
int version,
SeafDir *dir)
{
int ret = 0;
/* Don't need to save empty dir on disk. */
if (memcmp (dir->dir_id, EMPTY_SHA1, 40) == 0)
return 0;
if (seaf_obj_store_write_obj (fs_mgr->obj_store, repo_id, version, dir->dir_id,
dir->ondisk, dir->ondisk_size, FALSE) < 0)
ret = -1;
return ret;
}
SeafDir *
seaf_fs_manager_get_seafdir (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *dir_id)
{
void *data;
int len;
SeafDir *dir;
/* TODO: add hash cache */
if (memcmp (dir_id, EMPTY_SHA1, 40) == 0) {
dir = g_new0 (SeafDir, 1);
dir->version = version;
memset (dir->dir_id, '0', 40);
return dir;
}
if (seaf_obj_store_read_obj (mgr->obj_store, repo_id, version,
dir_id, &data, &len) < 0) {
seaf_warning ("[fs mgr] Failed to read dir %s.\n", dir_id);
return NULL;
}
dir = seaf_dir_from_data (dir_id, data, len, (version > 0));
g_free (data);
return dir;
}
static gint
compare_dirents (gconstpointer a, gconstpointer b)
{
const SeafDirent *denta = a, *dentb = b;
return strcmp (dentb->name, denta->name);
}
static gboolean
is_dirents_sorted (GList *dirents)
{
GList *ptr;
SeafDirent *dent, *dent_n;
gboolean ret = TRUE;
for (ptr = dirents; ptr != NULL; ptr = ptr->next) {
dent = ptr->data;
if (!ptr->next)
break;
dent_n = ptr->next->data;
/* If dirents are not sorted in descending order, return FALSE. */
if (strcmp (dent->name, dent_n->name) < 0) {
ret = FALSE;
break;
}
}
return ret;
}
SeafDir *
seaf_fs_manager_get_seafdir_sorted (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *dir_id)
{
SeafDir *dir = seaf_fs_manager_get_seafdir(mgr, repo_id, version, dir_id);
if (!dir)
return NULL;
/* Only some very old dir objects are not sorted. */
if (version > 0)
return dir;
if (!is_dirents_sorted (dir->entries))
dir->entries = g_list_sort (dir->entries, compare_dirents);
return dir;
}
SeafDir *
seaf_fs_manager_get_seafdir_sorted_by_path (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *root_id,
const char *path)
{
SeafDir *dir = seaf_fs_manager_get_seafdir_by_path (mgr, repo_id,
version, root_id,
path, NULL);
if (!dir)
return NULL;
/* Only some very old dir objects are not sorted. */
if (version > 0)
return dir;
if (!is_dirents_sorted (dir->entries))
dir->entries = g_list_sort (dir->entries, compare_dirents);
return dir;
}
static int
parse_metadata_type_v0 (const uint8_t *data, int len)
{
const uint8_t *ptr = data;
if (len < sizeof(guint32))
return SEAF_METADATA_TYPE_INVALID;
return (int)(get32bit(&ptr));
}
static int
parse_metadata_type_json (const char *obj_id, uint8_t *data, int len)
{
guint8 *decompressed;
int outlen;
json_t *object;
json_error_t error;
int type;
if (seaf_decompress (data, len, &decompressed, &outlen) < 0) {
seaf_warning ("Failed to decompress fs object %s.\n", obj_id);
return SEAF_METADATA_TYPE_INVALID;
}
object = json_loadb ((const char *)decompressed, outlen, 0, &error);
g_free (decompressed);
if (!object) {
seaf_warning ("Failed to load fs json object: %s.\n", error.text);
return SEAF_METADATA_TYPE_INVALID;
}
type = json_object_get_int_member (object, "type");
json_decref (object);
return type;
}
int
seaf_metadata_type_from_data (const char *obj_id,
uint8_t *data, int len, gboolean is_json)
{
if (is_json)
return parse_metadata_type_json (obj_id, data, len);
else
return parse_metadata_type_v0 (data, len);
}
SeafFSObject *
fs_object_from_v0_data (const char *obj_id, const uint8_t *data, int len)
{
int type = parse_metadata_type_v0 (data, len);
if (type == SEAF_METADATA_TYPE_FILE)
return (SeafFSObject *)seafile_from_v0_data (obj_id, data, len);
else if (type == SEAF_METADATA_TYPE_DIR)
return (SeafFSObject *)seaf_dir_from_v0_data (obj_id, data, len);
else {
seaf_warning ("Invalid object type %d.\n", type);
return NULL;
}
}
SeafFSObject *
fs_object_from_json (const char *obj_id, uint8_t *data, int len)
{
guint8 *decompressed;
int outlen;
json_t *object;
json_error_t error;
int type;
SeafFSObject *fs_obj;
if (seaf_decompress (data, len, &decompressed, &outlen) < 0) {
seaf_warning ("Failed to decompress fs object %s.\n", obj_id);
return NULL;
}
object = json_loadb ((const char *)decompressed, outlen, 0, &error);
g_free (decompressed);
if (!object) {
seaf_warning ("Failed to load fs json object: %s.\n", error.text);
return NULL;
}
type = json_object_get_int_member (object, "type");
if (type == SEAF_METADATA_TYPE_FILE)
fs_obj = (SeafFSObject *)seafile_from_json_object (obj_id, object);
else if (type == SEAF_METADATA_TYPE_DIR)
fs_obj = (SeafFSObject *)seaf_dir_from_json_object (obj_id, object);
else {
seaf_warning ("Invalid fs type %d.\n", type);
json_decref (object);
return NULL;
}
json_decref (object);
return fs_obj;
}
SeafFSObject *
seaf_fs_object_from_data (const char *obj_id,
uint8_t *data, int len,
gboolean is_json)
{
if (is_json)
return fs_object_from_json (obj_id, data, len);
else
return fs_object_from_v0_data (obj_id, data, len);
}
void
seaf_fs_object_free (SeafFSObject *obj)
{
if (!obj)
return;
if (obj->type == SEAF_METADATA_TYPE_FILE)
seafile_unref ((Seafile *)obj);
else if (obj->type == SEAF_METADATA_TYPE_DIR)
seaf_dir_free ((SeafDir *)obj);
}
BlockList *
block_list_new ()
{
BlockList *bl = g_new0 (BlockList, 1);
bl->block_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
bl->block_ids = g_ptr_array_new_with_free_func (g_free);
return bl;
}
void
block_list_free (BlockList *bl)
{
if (bl->block_hash)
g_hash_table_destroy (bl->block_hash);
g_ptr_array_free (bl->block_ids, TRUE);
g_free (bl);
}
void
block_list_insert (BlockList *bl, const char *block_id)
{
if (g_hash_table_lookup (bl->block_hash, block_id))
return;
char *key = g_strdup(block_id);
g_hash_table_replace (bl->block_hash, key, key);
g_ptr_array_add (bl->block_ids, g_strdup(block_id));
++bl->n_blocks;
}
BlockList *
block_list_difference (BlockList *bl1, BlockList *bl2)
{
BlockList *bl;
int i;
char *block_id;
char *key;
bl = block_list_new ();
for (i = 0; i < bl1->block_ids->len; ++i) {
block_id = g_ptr_array_index (bl1->block_ids, i);
if (g_hash_table_lookup (bl2->block_hash, block_id) == NULL) {
key = g_strdup(block_id);
g_hash_table_replace (bl->block_hash, key, key);
g_ptr_array_add (bl->block_ids, g_strdup(block_id));
++bl->n_blocks;
}
}
return bl;
}
static int
traverse_file (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *id,
TraverseFSTreeCallback callback,
void *user_data,
gboolean skip_errors)
{
gboolean stop = FALSE;
if (memcmp (id, EMPTY_SHA1, 40) == 0)
return 0;
if (!callback (mgr, repo_id, version, id, SEAF_METADATA_TYPE_FILE, user_data, &stop) &&
!skip_errors)
return -1;
return 0;
}
static int
traverse_dir (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *id,
TraverseFSTreeCallback callback,
void *user_data,
gboolean skip_errors)
{
SeafDir *dir;
GList *p;
SeafDirent *seaf_dent;
gboolean stop = FALSE;
if (!callback (mgr, repo_id, version,
id, SEAF_METADATA_TYPE_DIR, user_data, &stop) &&
!skip_errors)
return -1;
if (stop)
return 0;
dir = seaf_fs_manager_get_seafdir (mgr, repo_id, version, id);
if (!dir) {
seaf_warning ("[fs-mgr]get seafdir %s failed\n", id);
if (skip_errors)
return 0;
return -1;
}
for (p = dir->entries; p; p = p->next) {
seaf_dent = (SeafDirent *)p->data;
if (S_ISREG(seaf_dent->mode)) {
if (traverse_file (mgr, repo_id, version, seaf_dent->id,
callback, user_data, skip_errors) < 0) {
if (!skip_errors) {
seaf_dir_free (dir);
return -1;
}
}
} else if (S_ISDIR(seaf_dent->mode)) {
if (traverse_dir (mgr, repo_id, version, seaf_dent->id,
callback, user_data, skip_errors) < 0) {
if (!skip_errors) {
seaf_dir_free (dir);
return -1;
}
}
}
}
seaf_dir_free (dir);
return 0;
}
int
seaf_fs_manager_traverse_tree (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *root_id,
TraverseFSTreeCallback callback,
void *user_data,
gboolean skip_errors)
{
if (strcmp (root_id, EMPTY_SHA1) == 0) {
return 0;
}
return traverse_dir (mgr, repo_id, version, root_id, callback, user_data, skip_errors);
}
static int
traverse_dir_path (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *dir_path,
SeafDirent *dent,
TraverseFSPathCallback callback,
void *user_data)
{
SeafDir *dir;
GList *p;
SeafDirent *seaf_dent;
gboolean stop = FALSE;
char *sub_path;
int ret = 0;
if (!callback (mgr, dir_path, dent, user_data, &stop))
return -1;
if (stop)
return 0;
dir = seaf_fs_manager_get_seafdir (mgr, repo_id, version, dent->id);
if (!dir) {
seaf_warning ("get seafdir %s:%s failed\n", repo_id, dent->id);
return -1;
}
for (p = dir->entries; p; p = p->next) {
seaf_dent = (SeafDirent *)p->data;
sub_path = g_strconcat (dir_path, "/", seaf_dent->name, NULL);
if (S_ISREG(seaf_dent->mode)) {
if (!callback (mgr, sub_path, seaf_dent, user_data, &stop)) {
g_free (sub_path);
ret = -1;
break;
}
} else if (S_ISDIR(seaf_dent->mode)) {
if (traverse_dir_path (mgr, repo_id, version, sub_path, seaf_dent,
callback, user_data) < 0) {
g_free (sub_path);
ret = -1;
break;
}
}
g_free (sub_path);
}
seaf_dir_free (dir);
return ret;
}
int
seaf_fs_manager_traverse_path (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *root_id,
const char *dir_path,
TraverseFSPathCallback callback,
void *user_data)
{
SeafDirent *dent;
int ret = 0;
dent = seaf_fs_manager_get_dirent_by_path (mgr, repo_id, version,
root_id, dir_path, NULL);
if (!dent) {
seaf_warning ("Failed to get dirent for %.8s:%s.\n", repo_id, dir_path);
return -1;
}
ret = traverse_dir_path (mgr, repo_id, version, dir_path, dent,
callback, user_data);
seaf_dirent_free (dent);
return ret;
}
static gboolean
fill_blocklist (SeafFSManager *mgr,
const char *repo_id, int version,
const char *obj_id, int type,
void *user_data, gboolean *stop)
{
BlockList *bl = user_data;
Seafile *seafile;
int i;
if (type == SEAF_METADATA_TYPE_FILE) {
seafile = seaf_fs_manager_get_seafile (mgr, repo_id, version, obj_id);
if (!seafile) {
seaf_warning ("[fs mgr] Failed to find file %s.\n", obj_id);
return FALSE;
}
for (i = 0; i < seafile->n_blocks; ++i)
block_list_insert (bl, seafile->blk_sha1s[i]);
seafile_unref (seafile);
}
return TRUE;
}
int
seaf_fs_manager_populate_blocklist (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *root_id,
BlockList *bl)
{
return seaf_fs_manager_traverse_tree (mgr, repo_id, version, root_id,
fill_blocklist,
bl, FALSE);
}
gboolean
seaf_fs_manager_object_exists (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *id)
{
/* Empty file and dir always exists. */
if (memcmp (id, EMPTY_SHA1, 40) == 0)
return TRUE;
return seaf_obj_store_obj_exists (mgr->obj_store, repo_id, version, id);
}
void
seaf_fs_manager_delete_object (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *id)
{
seaf_obj_store_delete_obj (mgr->obj_store, repo_id, version, id);
}
gint64
seaf_fs_manager_get_file_size (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *file_id)
{
Seafile *file;
gint64 file_size;
file = seaf_fs_manager_get_seafile (seaf->fs_mgr, repo_id, version, file_id);
if (!file) {
seaf_warning ("Couldn't get file %s:%s\n", repo_id, file_id);
return -1;
}
file_size = file->file_size;
seafile_unref (file);
return file_size;
}
static gint64
get_dir_size (SeafFSManager *mgr, const char *repo_id, int version, const char *id)
{
SeafDir *dir;
SeafDirent *seaf_dent;
guint64 size = 0;
gint64 result;
GList *p;
dir = seaf_fs_manager_get_seafdir (mgr, repo_id, version, id);
if (!dir)
return -1;
for (p = dir->entries; p; p = p->next) {
seaf_dent = (SeafDirent *)p->data;
if (S_ISREG(seaf_dent->mode)) {
if (dir->version > 0)
result = seaf_dent->size;
else {
result = seaf_fs_manager_get_file_size (mgr,
repo_id,
version,
seaf_dent->id);
if (result < 0) {
seaf_dir_free (dir);
return result;
}
}
size += result;
} else if (S_ISDIR(seaf_dent->mode)) {
result = get_dir_size (mgr, repo_id, version, seaf_dent->id);
if (result < 0) {
seaf_dir_free (dir);
return result;
}
size += result;
}
}
seaf_dir_free (dir);
return size;
}
gint64
seaf_fs_manager_get_fs_size (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *root_id)
{
if (strcmp (root_id, EMPTY_SHA1) == 0)
return 0;
return get_dir_size (mgr, repo_id, version, root_id);
}
static int
count_dir_files (SeafFSManager *mgr, const char *repo_id, int version, const char *id)
{
SeafDir *dir;
SeafDirent *seaf_dent;
int count = 0;
int result;
GList *p;
dir = seaf_fs_manager_get_seafdir (mgr, repo_id, version, id);
if (!dir)
return -1;
for (p = dir->entries; p; p = p->next) {
seaf_dent = (SeafDirent *)p->data;
if (S_ISREG(seaf_dent->mode)) {
count ++;
} else if (S_ISDIR(seaf_dent->mode)) {
result = count_dir_files (mgr, repo_id, version, seaf_dent->id);
if (result < 0) {
seaf_dir_free (dir);
return result;
}
count += result;
}
}
seaf_dir_free (dir);
return count;
}
int
seaf_fs_manager_count_fs_files (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *root_id)
{
if (strcmp (root_id, EMPTY_SHA1) == 0)
return 0;
return count_dir_files (mgr, repo_id, version, root_id);
}
SeafDir *
seaf_fs_manager_get_seafdir_by_path (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *root_id,
const char *path,
GError **error)
{
SeafDir *dir;
SeafDirent *dent;
const char *dir_id = root_id;
char *name, *saveptr;
char *tmp_path = g_strdup(path);
dir = seaf_fs_manager_get_seafdir (mgr, repo_id, version, dir_id);
if (!dir) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_DIR_MISSING, "directory is missing");
return NULL;
}
name = strtok_r (tmp_path, "/", &saveptr);
while (name != NULL) {
GList *l;
for (l = dir->entries; l != NULL; l = l->next) {
dent = l->data;
if (strcmp(dent->name, name) == 0 && S_ISDIR(dent->mode)) {
dir_id = dent->id;
break;
}
}
if (!l) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_PATH_NO_EXIST,
"Path does not exists %s", path);
seaf_dir_free (dir);
dir = NULL;
break;
}
SeafDir *prev = dir;
dir = seaf_fs_manager_get_seafdir (mgr, repo_id, version, dir_id);
seaf_dir_free (prev);
if (!dir) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_DIR_MISSING,
"directory is missing");
break;
}
name = strtok_r (NULL, "/", &saveptr);
}
g_free (tmp_path);
return dir;
}
char *
seaf_fs_manager_path_to_obj_id (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *root_id,
const char *path,
guint32 *mode,
GError **error)
{
char *copy = g_strdup (path);
int off = strlen(copy) - 1;
char *slash, *name;
SeafDir *base_dir = NULL;
SeafDirent *dent;
GList *p;
char *obj_id = NULL;
while (off >= 0 && copy[off] == '/')
copy[off--] = 0;
if (strlen(copy) == 0) {
/* the path is root "/" */
if (mode) {
*mode = S_IFDIR;
}
obj_id = g_strdup(root_id);
goto out;
}
slash = strrchr (copy, '/');
if (!slash) {
base_dir = seaf_fs_manager_get_seafdir (mgr, repo_id, version, root_id);
if (!base_dir) {
seaf_warning ("Failed to find root dir %s.\n", root_id);
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, " ");
goto out;
}
name = copy;
} else {
*slash = 0;
name = slash + 1;
GError *tmp_error = NULL;
base_dir = seaf_fs_manager_get_seafdir_by_path (mgr,
repo_id,
version,
root_id,
copy,
&tmp_error);
if (tmp_error &&
!g_error_matches(tmp_error,
SEAFILE_DOMAIN,
SEAF_ERR_PATH_NO_EXIST)) {
seaf_warning ("Failed to get dir for %s.\n", copy);
g_propagate_error (error, tmp_error);
goto out;
}
/* The path doesn't exist in this commit. */
if (!base_dir) {
g_propagate_error (error, tmp_error);
goto out;
}
}
for (p = base_dir->entries; p != NULL; p = p->next) {
dent = p->data;
if (!is_object_id_valid (dent->id))
continue;
if (strcmp (dent->name, name) == 0) {
obj_id = g_strdup (dent->id);
if (mode) {
*mode = dent->mode;
}
break;
}
}
out:
if (base_dir)
seaf_dir_free (base_dir);
g_free (copy);
return obj_id;
}
char *
seaf_fs_manager_get_seafile_id_by_path (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *root_id,
const char *path,
GError **error)
{
guint32 mode;
char *file_id;
file_id = seaf_fs_manager_path_to_obj_id (mgr, repo_id, version,
root_id, path, &mode, error);
if (!file_id)
return NULL;
if (file_id && S_ISDIR(mode)) {
g_free (file_id);
return NULL;
}
return file_id;
}
char *
seaf_fs_manager_get_seafdir_id_by_path (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *root_id,
const char *path,
GError **error)
{
guint32 mode = 0;
char *dir_id;
dir_id = seaf_fs_manager_path_to_obj_id (mgr, repo_id, version,
root_id, path, &mode, error);
if (!dir_id)
return NULL;
if (dir_id && !S_ISDIR(mode)) {
g_free (dir_id);
return NULL;
}
return dir_id;
}
SeafDirent *
seaf_fs_manager_get_dirent_by_path (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *root_id,
const char *path,
GError **error)
{
SeafDirent *dent = NULL;
SeafDir *dir = NULL;
char *parent_dir = NULL;
char *file_name = NULL;
parent_dir = g_path_get_dirname(path);
file_name = g_path_get_basename(path);
if (strcmp (parent_dir, ".") == 0) {
dir = seaf_fs_manager_get_seafdir (mgr, repo_id, version, root_id);
if (!dir) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_DIR_MISSING, "directory is missing");
}
} else
dir = seaf_fs_manager_get_seafdir_by_path (mgr, repo_id, version,
root_id, parent_dir, error);
if (!dir) {
seaf_warning ("dir %s doesn't exist in repo %.8s.\n", parent_dir, repo_id);
goto out;
}
GList *p;
for (p = dir->entries; p; p = p->next) {
SeafDirent *d = p->data;
if (strcmp (d->name, file_name) == 0) {
dent = seaf_dirent_dup(d);
break;
}
}
out:
if (dir)
seaf_dir_free (dir);
g_free (parent_dir);
g_free (file_name);
return dent;
}
static gboolean
verify_seafdir_v0 (const char *dir_id, const uint8_t *data, int len,
gboolean verify_id)
{
guint32 meta_type;
guint32 mode;
char id[41];
guint32 name_len;
char name[SEAF_DIR_NAME_LEN];
const uint8_t *ptr;
int remain;
int dirent_base_size;
GChecksum *ctx;
uint8_t sha1[20];
gsize cs_len = 20;
char check_id[41];
if (len < sizeof(SeafdirOndisk)) {
seaf_warning ("[fs mgr] Corrupt seafdir object %s.\n", dir_id);
return FALSE;
}
ptr = data;
remain = len;
meta_type = get32bit (&ptr);
remain -= 4;
if (meta_type != SEAF_METADATA_TYPE_DIR) {
seaf_warning ("Data does not contain a directory.\n");
return FALSE;
}
if (verify_id) {
ctx = g_checksum_new (G_CHECKSUM_SHA1);
}
dirent_base_size = 2 * sizeof(guint32) + 40;
while (remain > dirent_base_size) {
mode = get32bit (&ptr);
memcpy (id, ptr, 40);
id[40] = '\0';
ptr += 40;
name_len = get32bit (&ptr);
remain -= dirent_base_size;
if (remain >= name_len) {
name_len = MIN (name_len, SEAF_DIR_NAME_LEN - 1);
memcpy (name, ptr, name_len);
ptr += name_len;
remain -= name_len;
} else {
seaf_warning ("Bad data format for dir objcet %s.\n", dir_id);
return FALSE;
}
if (verify_id) {
/* Convert mode to little endian before compute. */
if (G_BYTE_ORDER == G_BIG_ENDIAN)
mode = GUINT32_SWAP_LE_BE (mode);
g_checksum_update (ctx, (unsigned char *)id, 40);
g_checksum_update (ctx, (unsigned char *)name, name_len);
g_checksum_update (ctx, (unsigned char *)&mode, sizeof(mode));
}
}
if (!verify_id)
return TRUE;
g_checksum_get_digest (ctx, sha1, &cs_len);
rawdata_to_hex (sha1, check_id, 20);
g_checksum_free (ctx);
if (strcmp (check_id, dir_id) == 0)
return TRUE;
else
return FALSE;
}
static gboolean
verify_fs_object_json (const char *obj_id, uint8_t *data, int len)
{
guint8 *decompressed;
int outlen;
unsigned char sha1[20];
char hex[41];
if (seaf_decompress (data, len, &decompressed, &outlen) < 0) {
seaf_warning ("Failed to decompress fs object %s.\n", obj_id);
return FALSE;
}
calculate_sha1 (sha1, (const char *)decompressed, outlen);
rawdata_to_hex (sha1, hex, 20);
g_free (decompressed);
return (strcmp(hex, obj_id) == 0);
}
static gboolean
verify_seafdir (const char *dir_id, uint8_t *data, int len,
gboolean verify_id, gboolean is_json)
{
if (is_json)
return verify_fs_object_json (dir_id, data, len);
else
return verify_seafdir_v0 (dir_id, data, len, verify_id);
}
gboolean
seaf_fs_manager_verify_seafdir (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *dir_id,
gboolean verify_id,
gboolean *io_error)
{
void *data;
int len;
if (memcmp (dir_id, EMPTY_SHA1, 40) == 0) {
return TRUE;
}
if (seaf_obj_store_read_obj (mgr->obj_store, repo_id, version,
dir_id, &data, &len) < 0) {
seaf_warning ("[fs mgr] Failed to read dir %s:%s.\n", repo_id, dir_id);
*io_error = TRUE;
return FALSE;
}
gboolean ret = verify_seafdir (dir_id, data, len, verify_id, (version > 0));
g_free (data);
return ret;
}
static gboolean
verify_seafile_v0 (const char *id, const void *data, int len, gboolean verify_id)
{
const SeafileOndisk *ondisk = data;
GChecksum *ctx;
uint8_t sha1[20];
gsize cs_len = 20;
char check_id[41];
if (len < sizeof(SeafileOndisk)) {
seaf_warning ("[fs mgr] Corrupt seafile object %s.\n", id);
return FALSE;
}
if (ntohl(ondisk->type) != SEAF_METADATA_TYPE_FILE) {
seaf_warning ("[fd mgr] %s is not a file.\n", id);
return FALSE;
}
int id_list_length = len - sizeof(SeafileOndisk);
if (id_list_length % 20 != 0) {
seaf_warning ("[fs mgr] Bad seafile id list length %d.\n", id_list_length);
return FALSE;
}
if (!verify_id)
return TRUE;
ctx = g_checksum_new (G_CHECKSUM_SHA1);
g_checksum_update (ctx, ondisk->block_ids, len - sizeof(SeafileOndisk));
g_checksum_get_digest (ctx, sha1, &cs_len);
g_checksum_free (ctx);
rawdata_to_hex (sha1, check_id, 20);
if (strcmp (check_id, id) == 0)
return TRUE;
else
return FALSE;
}
static gboolean
verify_seafile (const char *id, void *data, int len,
gboolean verify_id, gboolean is_json)
{
if (is_json)
return verify_fs_object_json (id, data, len);
else
return verify_seafile_v0 (id, data, len, verify_id);
}
gboolean
seaf_fs_manager_verify_seafile (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *file_id,
gboolean verify_id,
gboolean *io_error)
{
void *data;
int len;
if (memcmp (file_id, EMPTY_SHA1, 40) == 0) {
return TRUE;
}
if (seaf_obj_store_read_obj (mgr->obj_store, repo_id, version,
file_id, &data, &len) < 0) {
seaf_warning ("[fs mgr] Failed to read file %s:%s.\n", repo_id, file_id);
*io_error = TRUE;
return FALSE;
}
gboolean ret = verify_seafile (file_id, data, len, verify_id, (version > 0));
g_free (data);
return ret;
}
static gboolean
verify_fs_object_v0 (const char *obj_id,
uint8_t *data,
int len,
gboolean verify_id)
{
gboolean ret = TRUE;
int type = seaf_metadata_type_from_data (obj_id, data, len, FALSE);
switch (type) {
case SEAF_METADATA_TYPE_FILE:
ret = verify_seafile_v0 (obj_id, data, len, verify_id);
break;
case SEAF_METADATA_TYPE_DIR:
ret = verify_seafdir_v0 (obj_id, data, len, verify_id);
break;
default:
seaf_warning ("Invalid meta data type: %d.\n", type);
return FALSE;
}
return ret;
}
gboolean
seaf_fs_manager_verify_object (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *obj_id,
gboolean verify_id,
gboolean *io_error)
{
void *data;
int len;
gboolean ret = TRUE;
if (memcmp (obj_id, EMPTY_SHA1, 40) == 0) {
return TRUE;
}
if (seaf_obj_store_read_obj (mgr->obj_store, repo_id, version,
obj_id, &data, &len) < 0) {
seaf_warning ("[fs mgr] Failed to read object %s:%s.\n", repo_id, obj_id);
*io_error = TRUE;
return FALSE;
}
if (version == 0)
ret = verify_fs_object_v0 (obj_id, data, len, verify_id);
else
ret = verify_fs_object_json (obj_id, data, len);
g_free (data);
return ret;
}
int
dir_version_from_repo_version (int repo_version)
{
if (repo_version == 0)
return 0;
else
return CURRENT_DIR_OBJ_VERSION;
}
int
seafile_version_from_repo_version (int repo_version)
{
if (repo_version == 0)
return 0;
else
return CURRENT_SEAFILE_OBJ_VERSION;
}
int
seaf_fs_manager_remove_store (SeafFSManager *mgr,
const char *store_id)
{
return seaf_obj_store_remove_store (mgr->obj_store, store_id);
}
seadrive-fuse-3.0.13/src/fs-mgr.h 0000664 0000000 0000000 00000026061 14761776747 0016532 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#ifndef SEAF_FILE_MGR_H
#define SEAF_FILE_MGR_H
#include
#include "obj-store.h"
#include "cdc.h"
#include "seafile-crypt.h"
#define CURRENT_DIR_OBJ_VERSION 1
#define CURRENT_SEAFILE_OBJ_VERSION 1
#define CDC_AVERAGE_BLOCK_SIZE (1 << 23) /* 8MB */
#define CDC_MIN_BLOCK_SIZE (6 * (1 << 20)) /* 6MB */
#define CDC_MAX_BLOCK_SIZE (10 * (1 << 20)) /* 10MB */
typedef struct _SeafFSManager SeafFSManager;
typedef struct _SeafFSObject SeafFSObject;
typedef struct _Seafile Seafile;
typedef struct _SeafDir SeafDir;
typedef struct _SeafDirent SeafDirent;
typedef enum {
SEAF_METADATA_TYPE_INVALID,
SEAF_METADATA_TYPE_FILE,
SEAF_METADATA_TYPE_LINK,
SEAF_METADATA_TYPE_DIR,
} SeafMetadataType;
/* Common to seafile and seafdir objects. */
struct _SeafFSObject {
int type;
};
struct _Seafile {
SeafFSObject object;
int version;
char file_id[41];
guint64 file_size;
guint32 n_blocks;
char **blk_sha1s;
int ref_count;
};
void
seafile_ref (Seafile *seafile);
void
seafile_unref (Seafile *seafile);
int
seafile_save (SeafFSManager *fs_mgr,
const char *repo_id,
int version,
Seafile *file);
#define SEAF_DIR_NAME_LEN 256
struct _SeafDirent {
int version;
guint32 mode;
char id[41];
guint32 name_len;
char *name;
/* attributes for version > 0 */
gint64 mtime;
char *modifier; /* for files only */
gint64 size; /* for files only */
};
struct _SeafDir {
SeafFSObject object;
int version;
char dir_id[41];
GList *entries;
/* data in on-disk format. */
void *ondisk;
int ondisk_size;
};
SeafDir *
seaf_dir_new (const char *id, GList *entries, int version);
void
seaf_dir_free (SeafDir *dir);
SeafDir *
seaf_dir_from_data (const char *dir_id, uint8_t *data, int len,
gboolean is_json);
void *
seaf_dir_to_data (SeafDir *dir, int *len);
int
seaf_dir_save (SeafFSManager *fs_mgr,
const char *repo_id,
int version,
SeafDir *dir);
SeafDirent *
seaf_dirent_new (int version, const char *sha1, int mode, const char *name,
gint64 mtime, const char *modifier, gint64 size);
void
seaf_dirent_free (SeafDirent *dent);
SeafDirent *
seaf_dirent_dup (SeafDirent *dent);
int
seaf_metadata_type_from_data (const char *obj_id,
uint8_t *data, int len, gboolean is_json);
/* Parse an fs object without knowing its type. */
SeafFSObject *
seaf_fs_object_from_data (const char *obj_id,
uint8_t *data, int len,
gboolean is_json);
void
seaf_fs_object_free (SeafFSObject *obj);
typedef struct {
/* TODO: GHashTable may be inefficient when we have large number of IDs. */
GHashTable *block_hash;
GPtrArray *block_ids;
uint32_t n_blocks;
uint32_t n_valid_blocks;
} BlockList;
BlockList *
block_list_new ();
void
block_list_free (BlockList *bl);
void
block_list_insert (BlockList *bl, const char *block_id);
/* Return a blocklist containing block ids which are in @bl1 but
* not in @bl2.
*/
BlockList *
block_list_difference (BlockList *bl1, BlockList *bl2);
struct _SeafileSession;
typedef struct _SeafFSManagerPriv SeafFSManagerPriv;
struct _SeafFSManager {
struct _SeafileSession *seaf;
struct SeafObjStore *obj_store;
SeafFSManagerPriv *priv;
};
SeafFSManager *
seaf_fs_manager_new (struct _SeafileSession *seaf,
const char *seaf_dir);
int
seaf_fs_manager_init (SeafFSManager *mgr);
/**
* Check in blocks and create seafile object.
* Returns file id for the seafile object in @file_id parameter.
*/
int
seaf_fs_manager_index_blocks (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *file_path,
SeafileCrypt *crypt,
gboolean write_data,
unsigned char sha1[]);
Seafile *
seaf_fs_manager_get_seafile (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *file_id);
SeafDir *
seaf_fs_manager_get_seafdir (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *dir_id);
/* Make sure entries in the returned dir is sorted in descending order.
*/
SeafDir *
seaf_fs_manager_get_seafdir_sorted (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *dir_id);
SeafDir *
seaf_fs_manager_get_seafdir_sorted_by_path (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *root_id,
const char *path);
int
seaf_fs_manager_populate_blocklist (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *root_id,
BlockList *bl);
/*
* For dir object, set *stop to TRUE to stop traversing the subtree.
*/
typedef gboolean (*TraverseFSTreeCallback) (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *obj_id,
int type,
void *user_data,
gboolean *stop);
int
seaf_fs_manager_traverse_tree (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *root_id,
TraverseFSTreeCallback callback,
void *user_data,
gboolean skip_errors);
typedef gboolean (*TraverseFSPathCallback) (SeafFSManager *mgr,
const char *path,
SeafDirent *dent,
void *user_data,
gboolean *stop);
int
seaf_fs_manager_traverse_path (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *root_id,
const char *dir_path,
TraverseFSPathCallback callback,
void *user_data);
gboolean
seaf_fs_manager_object_exists (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *id);
void
seaf_fs_manager_delete_object (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *id);
gint64
seaf_fs_manager_get_file_size (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *file_id);
gint64
seaf_fs_manager_get_fs_size (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *root_id);
int
seaf_fs_manager_count_fs_files (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *root_id);
SeafDir *
seaf_fs_manager_get_seafdir_by_path(SeafFSManager *mgr,
const char *repo_id,
int version,
const char *root_id,
const char *path,
GError **error);
char *
seaf_fs_manager_get_seafile_id_by_path (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *root_id,
const char *path,
GError **error);
char *
seaf_fs_manager_path_to_obj_id (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *root_id,
const char *path,
guint32 *mode,
GError **error);
char *
seaf_fs_manager_get_seafdir_id_by_path (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *root_id,
const char *path,
GError **error);
SeafDirent *
seaf_fs_manager_get_dirent_by_path (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *root_id,
const char *path,
GError **error);
/* Check object integrity. */
gboolean
seaf_fs_manager_verify_seafdir (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *dir_id,
gboolean verify_id,
gboolean *io_error);
gboolean
seaf_fs_manager_verify_seafile (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *file_id,
gboolean verify_id,
gboolean *io_error);
gboolean
seaf_fs_manager_verify_object (SeafFSManager *mgr,
const char *repo_id,
int version,
const char *obj_id,
gboolean verify_id,
gboolean *io_error);
int
dir_version_from_repo_version (int repo_version);
int
seafile_version_from_repo_version (int repo_version);
struct _CDCFileDescriptor;
void
seaf_fs_manager_calculate_seafile_id_json (int repo_version,
struct _CDCFileDescriptor *cdc,
guint8 *file_id_sha1);
int
seaf_fs_manager_remove_store (SeafFSManager *mgr,
const char *store_id);
#endif
seadrive-fuse-3.0.13/src/fuse-ops.c 0000664 0000000 0000000 00000146007 14761776747 0017076 0 ustar 00root root 0000000 0000000 #include "common.h"
#if defined __linux__ || defined __APPLE__
#define FUSE_USE_VERSION 26
#include
#ifdef __APPLE__
#include
#endif
#include "seafile-session.h"
#include "fuse-ops.h"
#define DEBUG_FLAG SEAFILE_DEBUG_FS
#include "log.h"
struct _FusePathComps {
RepoType repo_type;
RepoInfo *repo_info;
char *repo_path;
char *root_path;
// account_info will not be set only if there are multiple accounts and it is the root directory.
AccountInfo *account_info;
gboolean multi_account;
};
typedef struct _FusePathComps FusePathComps;
/*
* Input path and return result can be:
* 1. root dir -> return 0, repo_type == 0
* 2. category dir -> return 0, repo_type == TYPE, repo_info == NULL, repo_path == NULL
* 3. new category dir -> return -ENOENT, repo_type == 0
* 4. repo dir -> return 0, repo_type == TYPE, repo_info != NULL, repo_path == NULL
* 5. new repo dir && repo_path
* -> return -ENOENT, repo_type == TYPE, repo_info == NULL, repo_path == NULL
* 6. new repo dir && !repo_path
* -> return 0, repo_type == TYPE, repo_info == NULL, repo_path == NULL, root_path == new repo dir
* 7. repo path -> return 0, repo_type == TYPE, repo_info != NULL, repo_path != NULL
*
* Note that case 2 and 6 are quite similar. The only difference is whether root_path is set.
* If root_path is set, repo_info and repo_path are always NULL.
*/
static int
parse_fuse_path_single_account (SeafAccount *account, const char *path, FusePathComps *path_comps)
{
char *path_nfc = NULL;
char **tokens;
int n;
char *repo_name, *display_name;
RepoInfo *info;
int ret = 0;
path_comps->repo_info = NULL;
path_comps->repo_path = NULL;
path_nfc = g_utf8_normalize (path, -1, G_NORMALIZE_NFC);
if (!path_nfc)
return -ENOENT;
if (*path_nfc == '/')
path = path_nfc + 1;
else
path = path_nfc;
tokens = g_strsplit (path, "/", 3);
n = g_strv_length (tokens);
switch (n) {
case 0:
break;
case 1:
path_comps->repo_type = repo_type_from_string (tokens[0]);
if (path_comps->repo_type == REPO_TYPE_UNKNOWN) {
ret = -ENOENT;
goto out;
}
break;
case 2:
path_comps->repo_type = repo_type_from_string (tokens[0]);
if (path_comps->repo_type == REPO_TYPE_UNKNOWN) {
ret = -ENOENT;
goto out;
}
repo_name = tokens[1];
display_name = g_strconcat (tokens[0], "/", tokens[1], NULL);
info = seaf_repo_manager_get_repo_info_by_display_name (seaf->repo_mgr, account->server, account->username, display_name);
g_free (display_name);
if (!info) {
path_comps->root_path = g_strdup (repo_name);
goto out;
}
path_comps->repo_info = info;
break;
case 3:
path_comps->repo_type = repo_type_from_string (tokens[0]);
if (path_comps->repo_type == REPO_TYPE_UNKNOWN) {
ret = -ENOENT;
goto out;
}
repo_name = tokens[1];
display_name = g_strconcat (tokens[0], "/", tokens[1], NULL);
info = seaf_repo_manager_get_repo_info_by_display_name (seaf->repo_mgr, account->server, account->username, display_name);
g_free (display_name);
if (!info) {
ret = -ENOENT;
goto out;
}
path_comps->repo_info = info;
path_comps->repo_path = g_strdup(tokens[2]);
break;
}
out:
g_free (path_nfc);
g_strfreev (tokens);
return ret;
}
/*
* Input path and return result can be:
* 1. root dir -> return 0, repo_type == 0, account_info == NULL
* 2. accounts dir -> return 0, repo_type == 0, account_info != NULL
* 3. category dir -> return 0, repo_type == TYPE, repo_info == NULL, repo_path == NULL
* 4. new category dir -> return -ENOENT, repo_type == 0
* 5. repo dir -> return 0, repo_type == TYPE, repo_info != NULL, repo_path == NULL
* 6. new repo dir && repo_path
* -> return -ENOENT, repo_type == TYPE, repo_info == NULL, repo_path == NULL
* 7. new repo dir && !repo_path
* -> return 0, repo_type == TYPE, repo_info == NULL, repo_path == NULL, root_path == new repo dir
* 8. repo path -> return 0, repo_type == TYPE, repo_info != NULL, repo_path != NULL
*
* Note that case 3 and 7 are quite similar. The only difference is whether root_path is set.
* If root_path is set, repo_info and repo_path are always NULL.
*/
static int
parse_fuse_path_multi_account (const char *path, FusePathComps *path_comps)
{
char *path_nfc = NULL;
char **tokens;
int n;
char *repo_name, *display_name;
RepoInfo *info;
int ret = 0;
AccountInfo *account_info = NULL;
path_comps->repo_info = NULL;
path_comps->repo_path = NULL;
path_nfc = g_utf8_normalize (path, -1, G_NORMALIZE_NFC);
if (!path_nfc)
return -ENOENT;
if (*path_nfc == '/')
path = path_nfc + 1;
else
path = path_nfc;
tokens = g_strsplit (path, "/", 4);
n = g_strv_length (tokens);
switch (n) {
case 0:
break;
case 1:
account_info = seaf_repo_manager_get_account_info_by_name (seaf->repo_mgr, tokens[0]);
if (!account_info) {
ret = -ENOENT;
goto out;
}
path_comps->account_info = account_info;
break;
case 2:
account_info = seaf_repo_manager_get_account_info_by_name (seaf->repo_mgr, tokens[0]);
if (!account_info) {
ret = -ENOENT;
goto out;
}
path_comps->repo_type = repo_type_from_string (tokens[1]);
if (path_comps->repo_type == REPO_TYPE_UNKNOWN) {
ret = -ENOENT;
goto out;
}
path_comps->account_info = account_info;
break;
case 3:
account_info = seaf_repo_manager_get_account_info_by_name (seaf->repo_mgr, tokens[0]);
if (!account_info) {
ret = -ENOENT;
goto out;
}
path_comps->repo_type = repo_type_from_string (tokens[1]);
if (path_comps->repo_type == REPO_TYPE_UNKNOWN) {
ret = -ENOENT;
goto out;
}
path_comps->account_info = account_info;
repo_name = tokens[2];
display_name = g_strconcat (tokens[1], "/", tokens[2], NULL);
info = seaf_repo_manager_get_repo_info_by_display_name (seaf->repo_mgr, account_info->server, account_info->username, display_name);
g_free (display_name);
if (!info) {
path_comps->root_path = g_strdup (repo_name);
goto out;
}
path_comps->repo_info = info;
break;
case 4:
account_info = seaf_repo_manager_get_account_info_by_name (seaf->repo_mgr, tokens[0]);
if (!account_info) {
ret = -ENOENT;
goto out;
}
path_comps->repo_type = repo_type_from_string (tokens[1]);
if (path_comps->repo_type == REPO_TYPE_UNKNOWN) {
ret = -ENOENT;
goto out;
}
repo_name = tokens[2];
display_name = g_strconcat (tokens[1], "/", tokens[2], NULL);
info = seaf_repo_manager_get_repo_info_by_display_name (seaf->repo_mgr, account_info->server, account_info->username, display_name);
g_free (display_name);
if (!info) {
ret = -ENOENT;
goto out;
}
path_comps->account_info = account_info;
path_comps->repo_info = info;
path_comps->repo_path = g_strdup(tokens[3]);
break;
}
out:
if (ret != 0 && account_info) {
account_info_free (account_info);
}
g_free (path_nfc);
g_strfreev (tokens);
return ret;
}
static int
parse_fuse_path (const char *path, FusePathComps *path_comps)
{
int ret = 0;
GList *accounts = NULL;
SeafAccount *account;
accounts = seaf_repo_manager_get_account_list (seaf->repo_mgr);
if (!accounts) {
ret = -ENOENT;
goto out;
}
if (g_list_length (accounts) <= 1) {
path_comps->multi_account = FALSE;
account = accounts->data;
ret = parse_fuse_path_single_account (account, path, path_comps);
if (ret == 0) {
AccountInfo *account_info = g_new0 (AccountInfo, 1);
account_info->server = g_strdup (account->server);
account_info->username = g_strdup (account->username);
path_comps->account_info = account_info;
}
} else {
path_comps->multi_account = TRUE;
ret = parse_fuse_path_multi_account (path, path_comps);
}
out:
if (accounts)
g_list_free_full (accounts, (GDestroyNotify)seaf_account_free);
return ret;
}
static void
path_comps_free (FusePathComps *comps)
{
if (!comps)
return;
if (comps->root_path) {
g_free (comps->root_path);
} else {
repo_info_free (comps->repo_info);
g_free (comps->repo_path);
}
if (comps->account_info)
account_info_free (comps->account_info);
}
static void
notify_fs_op_error (const char *type, const char *path)
{
json_t *msg = json_object();
json_object_set_string_member (msg, "type", type);
json_object_set_string_member (msg, "path", path);
mq_mgr_push_msg (seaf->mq_mgr, SEADRIVE_EVENT_CHAN, msg);
}
static gint64
get_category_dir_mtime (const char *server, const char *user, RepoType type)
{
GList *infos, *ptr;
RepoInfo *info;
gint64 mtime = 0;
infos = seaf_repo_manager_get_account_repos (seaf->repo_mgr, server, user);
for (ptr = infos; ptr; ptr = ptr->next) {
info = ptr->data;
if (type != REPO_TYPE_UNKNOWN && info->type != type)
continue;
if (info->mtime > mtime)
mtime = info->mtime;
}
g_list_free_full (infos, (GDestroyNotify)repo_info_free);
return mtime;
}
int
seadrive_fuse_getattr(const char *path, struct stat *stbuf)
{
FusePathComps comps;
int ret = 0;
uid_t uid;
gid_t gid;
SeafRepo *repo = NULL;
RepoTreeStat st;
seaf_debug ("getattr %s called.\n", path);
if (!seaf->started)
return -ENOENT;
memset (stbuf, 0, sizeof(struct stat));
memset (&comps, 0, sizeof(comps));
if (parse_fuse_path (path, &comps) < 0) {
return -ENOENT;
}
/* Set file/folder owner/group to the process's euid and egid. */
uid = geteuid();
gid = getegid();
if (comps.root_path) {
g_free (comps.root_path);
return -ENOENT;
}
if (!comps.repo_type) {
/* Root or account directory */
stbuf->st_mode = S_IFDIR | 0755;
stbuf->st_nlink = 2;
stbuf->st_size = 4096;
stbuf->st_uid = uid;
stbuf->st_gid = gid;
if (comps.multi_account && comps.account_info)
// Multiple account directory
stbuf->st_mtime = get_category_dir_mtime (comps.account_info->server, comps.account_info->username, comps.repo_type);
} else if (!comps.repo_info && !comps.repo_path) {
/* Category directory */
stbuf->st_mode = S_IFDIR | 0755;
stbuf->st_nlink = 2;
stbuf->st_size = 4096;
stbuf->st_uid = uid;
stbuf->st_gid = gid;
stbuf->st_mtime = get_category_dir_mtime (comps.account_info->server, comps.account_info->username, comps.repo_type);
} else if (!comps.repo_path) {
/* Repo directory */
stbuf->st_mode = S_IFDIR | 0755;
stbuf->st_nlink = 2;
stbuf->st_size = 4096;
stbuf->st_mtime = comps.repo_info->mtime;
stbuf->st_uid = uid;
stbuf->st_gid = gid;
} else {
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, comps.repo_info->id);
if (!repo || !repo->fs_ready) {
ret = -ENOENT;
goto out;
}
int rc = repo_tree_stat_path (repo->tree, comps.repo_path, &st);
if (rc < 0) {
ret = rc;
goto out;
}
gboolean is_writable = (seaf_repo_manager_is_path_writable(seaf->repo_mgr,
comps.repo_info->id,
comps.repo_path) &&
!seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,
comps.repo_info->id,
comps.repo_path));
if (S_ISDIR(st.mode)) {
stbuf->st_size = 4096;
if (is_writable)
stbuf->st_mode = S_IFDIR | 0755;
else
stbuf->st_mode = S_IFDIR | 0555;
} else {
stbuf->st_size = st.size;
if (is_writable)
stbuf->st_mode = S_IFREG | 0644;
else
stbuf->st_mode = S_IFREG | 0444;
}
stbuf->st_mtime = st.mtime;
stbuf->st_nlink = 1;
stbuf->st_uid = uid;
stbuf->st_gid = gid;
}
out:
seaf_repo_unref (repo);
path_comps_free (&comps);
return ret;
}
static int
readdir_root_accounts (void *buf, fuse_fill_dir_t filler)
{
GList *accounts = NULL, *ptr;
SeafAccount *account;
accounts = seaf_repo_manager_get_account_list (seaf->repo_mgr);
if (!accounts) {
return 0;
}
for (ptr = accounts; ptr; ptr = ptr->next) {
account = ptr->data;
filler (buf, account->name, NULL, 0);
}
g_list_free_full (accounts, (GDestroyNotify)seaf_account_free);
return 0;
}
static int
readdir_root (AccountInfo *account, void *buf, fuse_fill_dir_t filler)
{
GList *types = repo_type_string_list ();
GList *ptr;
char *type_str;
#ifdef __APPLE__
char *dname_nfd;
#endif
for (ptr = types; ptr; ptr = ptr->next) {
type_str = ptr->data;
#ifdef __APPLE__
dname_nfd = g_utf8_normalize (type_str, -1, G_NORMALIZE_NFD);
filler (buf, dname_nfd, NULL, 0);
g_free (dname_nfd);
#else
filler (buf, type_str, NULL, 0);
#endif
}
g_list_free_full (types, g_free);
return 0;
}
static int
readdir_category (AccountInfo *account, RepoType type, void *buf, fuse_fill_dir_t filler)
{
GList *repos = seaf_repo_manager_get_account_repos (seaf->repo_mgr, account->server, account->username);
GList *ptr;
RepoInfo *info;
SeafRepo *repo;
char *dname;
#ifdef __APPLE__
char *dname_nfd;
#endif
for (ptr = repos; ptr; ptr = ptr->next) {
info = ptr->data;
if (info->type != type)
continue;
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, info->id);
if (repo && repo->encrypted && !repo->is_passwd_set) {
seaf_repo_unref (repo);
continue;
}
dname = g_path_get_basename (info->display_name);
#ifdef __APPLE__
dname_nfd = g_utf8_normalize (dname, -1, G_NORMALIZE_NFD);
filler (buf, dname_nfd, NULL, 0);
g_free (dname_nfd);
#else
filler (buf, dname, NULL, 0);
#endif
g_free (dname);
seaf_repo_unref (repo);
}
g_list_free_full (repos, (GDestroyNotify)repo_info_free);
return 0;
}
static int
readdir_repo (const char *repo_id, const char *path, void *buf, fuse_fill_dir_t filler)
{
SeafRepo *repo = NULL;
GHashTable *dirents;
GHashTableIter iter;
gpointer key, value;
char *dname;
int ret = 0;
#ifdef __APPLE__
char *dname_nfd;
#endif
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo || !repo->fs_ready) {
ret = -ENOENT;
goto out;
}
if (repo->encrypted && !repo->is_passwd_set) {
ret = -ENOENT;
goto out;
}
dirents = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
int rc = repo_tree_readdir (repo->tree, path, dirents);
if (rc < 0) {
ret = rc;
goto out;
}
g_hash_table_iter_init (&iter, dirents);
while (g_hash_table_iter_next (&iter, &key, &value)) {
dname = (char *)key;
if (seaf_repo_manager_is_path_invisible (seaf->repo_mgr, repo_id, dname)) {
continue;
}
filler (buf, dname, NULL, 0);
}
g_hash_table_destroy (dirents);
out:
seaf_repo_unref (repo);
return ret;
}
int
seadrive_fuse_readdir(const char *path, void *buf,
fuse_fill_dir_t filler, off_t offset,
struct fuse_file_info *info)
{
FusePathComps comps;
int ret = 0;
seaf_debug ("readdir %s called.\n", path);
if (!seaf->started)
return 0;
filler(buf, ".", NULL, 0);
filler(buf, "..", NULL, 0);
memset (&comps, 0, sizeof(comps));
if (parse_fuse_path (path, &comps) < 0) {
return -ENOENT;
}
if (comps.multi_account) {
if (comps.root_path) {
ret = -ENOENT;
} else if (!comps.account_info) {
/* Root directory */
ret = readdir_root_accounts (buf, filler);
} else if (!comps.repo_type) {
/* Account directory */
ret = readdir_root (comps.account_info, buf, filler);
} else if (!comps.repo_info && !comps.repo_path) {
/* Category directory */
seaf->last_access_fs_time = (gint64)time(NULL);
ret = readdir_category (comps.account_info, comps.repo_type, buf, filler);
} else if (!comps.repo_path) {
/* Repo directory */
seaf->last_access_fs_time = (gint64)time(NULL);
ret = readdir_repo (comps.repo_info->id, "", buf, filler);
} else {
seaf->last_access_fs_time = (gint64)time(NULL);
ret = readdir_repo (comps.repo_info->id, comps.repo_path, buf, filler);
}
} else {
if (comps.root_path) {
ret = -ENOENT;
} else if (!comps.repo_type) {
/* Root directory */
ret = readdir_root (comps.account_info, buf, filler);
} else if (!comps.repo_info && !comps.repo_path) {
/* Category directory */
seaf->last_access_fs_time = (gint64)time(NULL);
ret = readdir_category (comps.account_info, comps.repo_type, buf, filler);
} else if (!comps.repo_path) {
/* Repo directory */
seaf->last_access_fs_time = (gint64)time(NULL);
ret = readdir_repo (comps.repo_info->id, "", buf, filler);
} else {
seaf->last_access_fs_time = (gint64)time(NULL);
ret = readdir_repo (comps.repo_info->id, comps.repo_path, buf, filler);
}
}
path_comps_free (&comps);
return ret;
}
static int
repo_mknod (const char *repo_id, const char *path, mode_t mode)
{
SeafRepo *repo = NULL;
gint64 mtime;
JournalOp *op;
int ret = 0;
char *office_path;
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo || !repo->fs_ready) {
ret = -ENOENT;
goto out;
}
if (!seaf_repo_manager_is_path_writable (seaf->repo_mgr, repo_id, path)) {
ret = -EACCES;
goto out;
}
mtime = (gint64)time(NULL);
int rc = repo_tree_create_file (repo->tree, path, EMPTY_SHA1, (guint32)mode, mtime, 0);
if (rc < 0) {
ret = rc;
goto out;
}
op = journal_op_new (OP_TYPE_CREATE_FILE, path, NULL, 0, mtime, mode);
if (journal_append_op (repo->journal, op) < 0) {
seaf_warning ("Failed to append operation to journal of repo %s.\n",
repo_id);
ret = -ENOMEM;
journal_op_free (op);
goto out;
}
office_path = NULL;
if (repo_tree_is_office_lock_file (repo->tree, path, &office_path))
seaf_sync_manager_lock_file_on_server (seaf->sync_mgr, repo->server, repo->user, repo->id, office_path);
g_free (office_path);
out:
seaf_repo_unref (repo);
return ret;
}
int seadrive_fuse_mknod (const char *path, mode_t mode, dev_t dev)
{
FusePathComps comps;
int ret = 0;
seaf_debug ("mknod %s called. mode = %o.\n", path, mode);
if (!seaf->started)
return 0;
memset (&comps, 0, sizeof(comps));
if (parse_fuse_path (path, &comps) < 0) {
if (!comps.repo_type)
return -EACCES;
else
return -ENOENT;
}
if (!comps.account_info) {
ret = -EACCES;
} else if (!comps.repo_type) {
ret = -EACCES;
} else if (comps.root_path) {
/* Don't allow creating files in category. */
ret = -EACCES;
} else if (!comps.repo_info && !comps.repo_path) {
/* Category directory */
ret = -EACCES;
} else if (!comps.repo_path) {
/* Repo directory */
ret = -EACCES;
} else {
if (!S_ISREG(mode)) {
ret = -EINVAL;
goto out;
}
seaf->last_access_fs_time = (gint64)time(NULL);
ret = repo_mknod (comps.repo_info->id, comps.repo_path, mode);
}
out:
path_comps_free (&comps);
return ret;
}
static int
repo_mkdir (const char *repo_id, const char *path)
{
SeafRepo *repo = NULL;
gint64 mtime;
JournalOp *op;
int ret = 0;
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo || !repo->fs_ready) {
ret = -ENOENT;
goto out;
}
if (!seaf_repo_manager_is_path_writable (seaf->repo_mgr, repo_id, path)) {
ret = -EACCES;
goto out;
}
mtime = (gint64)time(NULL);
int rc = repo_tree_mkdir (repo->tree, path, mtime);
if (rc < 0) {
ret = rc;
goto out;
}
op = journal_op_new (OP_TYPE_MKDIR, path, NULL, 0, mtime, 0);
if (journal_append_op (repo->journal, op) < 0) {
seaf_warning ("Failed to append operation to journal of repo %s.\n",
repo_id);
ret = -ENOMEM;
journal_op_free (op);
}
file_cache_mgr_mkdir (seaf->file_cache_mgr, repo_id, path);
out:
seaf_repo_unref (repo);
return ret;
}
#define TRASH_PREFIX ".Trash"
int seadrive_fuse_mkdir (const char *path, mode_t mode)
{
FusePathComps comps;
int ret = 0;
seaf_debug ("mkdir %s called.\n", path);
if (!seaf->started)
return 0;
memset (&comps, 0, sizeof(comps));
if (parse_fuse_path (path, &comps) < 0) {
if (!comps.repo_type)
return -EACCES;
else
return -ENOENT;
}
if (!comps.account_info) {
ret = -EACCES;
}else if (!comps.repo_type) {
/* Root directory */
ret = -EACCES;
} else if (comps.root_path) {
if (strncmp (comps.root_path, TRASH_PREFIX, strlen(TRASH_PREFIX)) == 0) {
// Don't create trash dir now, or will lose delete event
ret = -EACCES;
} else if (comps.repo_type != REPO_TYPE_MINE) {
/* Don't allow creating repo under shared or group categories. */
ret = -EACCES;
} else {
// Make root dir to create repo
seaf->last_access_fs_time = (gint64)time(NULL);
ret = seaf_sync_manager_create_repo (seaf->sync_mgr,
comps.account_info->server,
comps.account_info->username,
comps.root_path);
if (ret < 0)
ret = -EIO;
}
} else if (!comps.repo_info && !comps.repo_path) {
/* Category directory */
ret = -EEXIST;
} else if (!comps.repo_path) {
/* Repo directory */
ret = -EEXIST;
} else {
seaf->last_access_fs_time = (gint64)time(NULL);
ret = repo_mkdir (comps.repo_info->id, comps.repo_path);
}
path_comps_free (&comps);
return ret;
}
static int
repo_unlink (const char *repo_id, const char *path)
{
SeafRepo *repo = NULL;
JournalOp *op;
int ret = 0;
char *office_path;
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo || !repo->fs_ready) {
ret = -ENOENT;
goto out;
}
if (!seaf_repo_manager_is_path_writable (seaf->repo_mgr, repo_id, path)) {
ret = -EACCES;
goto out;
}
if (seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,
repo_id, path)) {
ret = -EACCES;
goto out;
}
office_path = NULL;
if (repo_tree_is_office_lock_file (repo->tree, path, &office_path))
seaf_sync_manager_unlock_file_on_server (seaf->sync_mgr, repo->server, repo->user, repo->id, office_path);
g_free (office_path);
int rc = repo_tree_unlink (repo->tree, path);
if (rc < 0) {
ret = rc;
goto out;
}
op = journal_op_new (OP_TYPE_DELETE_FILE, path, NULL, 0, 0, 0);
if (journal_append_op (repo->journal, op) < 0) {
seaf_warning ("Failed to append operation to journal of repo %s.\n",
repo_id);
ret = -ENOMEM;
journal_op_free (op);
goto out;
}
file_cache_mgr_unlink (seaf->file_cache_mgr, repo_id, path);
out:
seaf_repo_unref (repo);
return ret;
}
int seadrive_fuse_unlink (const char *path)
{
FusePathComps comps;
int ret = 0;
seaf_debug ("unlink %s called.\n", path);
if (!seaf->started)
return 0;
memset (&comps, 0, sizeof(comps));
if (parse_fuse_path (path, &comps) < 0) {
return -ENOENT;
}
if (!comps.account_info) {
ret = -EACCES;
} else if (!comps.repo_type) {
/* Root directory */
ret = -EACCES;
} else if (comps.root_path) {
ret = -EACCES;
} else if (!comps.repo_info && !comps.repo_path) {
/* Category directory */
ret = -EACCES;
} else if (!comps.repo_path) {
/* Repo directory */
ret = -EACCES;
} else {
seaf->last_access_fs_time = (gint64)time(NULL);
ret = repo_unlink (comps.repo_info->id, comps.repo_path);
}
if (ret == 0) {
seaf_sync_manager_delete_active_path (seaf->sync_mgr,
comps.repo_info->id,
comps.repo_path);
}
path_comps_free (&comps);
return ret;
}
static int
repo_rmdir (const char *repo_id, const char *path)
{
SeafRepo *repo = NULL;
JournalOp *op;
int ret = 0;
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo || !repo->fs_ready) {
ret = -ENOENT;
goto out;
}
if (!seaf_repo_manager_is_path_writable (seaf->repo_mgr, repo_id, path)) {
ret = -EACCES;
goto out;
}
int rc = repo_tree_rmdir (repo->tree, path);
if (rc < 0) {
ret = rc;
goto out;
}
op = journal_op_new (OP_TYPE_RMDIR, path, NULL, 0, 0, 0);
if (journal_append_op (repo->journal, op) < 0) {
seaf_warning ("Failed to append operation to journal of repo %s.\n",
repo_id);
ret = -ENOMEM;
journal_op_free (op);
goto out;
}
file_cache_mgr_rmdir (seaf->file_cache_mgr, repo_id, path);
out:
seaf_repo_unref (repo);
return ret;
}
int seadrive_fuse_rmdir (const char *path)
{
FusePathComps comps;
int ret = 0;
seaf_debug ("rmdir %s called.\n", path);
if (!seaf->started)
return 0;
memset (&comps, 0, sizeof(comps));
if (parse_fuse_path (path, &comps) < 0) {
return -ENOENT;
}
if (!comps.account_info) {
ret = -EACCES;
} else if (!comps.repo_type) {
ret = -EACCES;
} else if (comps.root_path) {
ret = -EACCES;
} else if (!comps.repo_info && !comps.repo_path) {
/* Root directory */
ret = -EACCES;
} else if (!comps.repo_path) {
/* Repo directory */
ret = seaf_repo_manager_check_delete_repo (comps.repo_info->id, comps.repo_type);
if (ret < 0)
goto out;
ret = seaf_sync_manager_delete_repo (seaf->sync_mgr,
comps.account_info->server,
comps.account_info->username,
comps.repo_info->id);
if (ret < 0)
ret = -EIO;
goto out;
} else {
seaf->last_access_fs_time = (gint64)time(NULL);
ret = repo_rmdir (comps.repo_info->id, comps.repo_path);
}
if (ret == 0) {
seaf_sync_manager_delete_active_path (seaf->sync_mgr,
comps.repo_info->id,
comps.repo_path);
}
out:
path_comps_free (&comps);
return ret;
}
static int
repo_rename (const char *repo_id, const char *oldpath, const char *newpath)
{
SeafRepo *repo = NULL;
JournalOp *op;
int ret = 0;
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo || !repo->fs_ready) {
ret = -ENOENT;
goto out;
}
int rc = repo_tree_rename (repo->tree, oldpath, newpath, TRUE);
if (rc < 0) {
ret = rc;
goto out;
}
op = journal_op_new (OP_TYPE_RENAME, oldpath, newpath, 0, 0, 0);
if (journal_append_op (repo->journal, op) < 0) {
seaf_warning ("Failed to append operation to journal of repo %s.\n",
repo_id);
ret = -ENOMEM;
journal_op_free (op);
goto out;
}
file_cache_mgr_rename (seaf->file_cache_mgr, repo_id, oldpath, repo_id, newpath);
out:
seaf_repo_unref (repo);
return ret;
}
typedef struct CrossRepoRenameData {
char *server;
char *user;
char *repo_id1;
char *oldpath;
char *repo_id2;
char *newpath;
gboolean is_file;
} CrossRepoRenameData;
static void
cross_repo_rename_data_free (CrossRepoRenameData *data)
{
if (!data)
return;
g_free (data->server);
g_free (data->user);
g_free (data->repo_id1);
g_free (data->repo_id2);
g_free (data->oldpath);
g_free (data->newpath);
g_free (data);
}
static void
notify_cross_repo_move (const char *src_repo_id, const char *src_path,
const char *dst_repo_id, const char *dst_path,
gboolean is_start, gboolean failed)
{
json_t *msg = json_object ();
char *src_repo_name = NULL, *dst_repo_name = NULL;
char *srcpath = NULL, *dstpath = NULL;
if (is_start)
json_object_set_string_member (msg, "type", "cross-repo-move.start");
else if (!failed)
json_object_set_string_member (msg, "type", "cross-repo-move.done");
else
json_object_set_string_member (msg, "type", "cross-repo-move.error");
src_repo_name = seaf_repo_manager_get_repo_display_name (seaf->repo_mgr,
src_repo_id);
if (!src_repo_name) {
goto out;
}
srcpath = g_strconcat (src_repo_name, "/", src_path, NULL);
dst_repo_name = seaf_repo_manager_get_repo_display_name (seaf->repo_mgr,
dst_repo_id);
if (!dst_repo_name) {
goto out;
}
dstpath = g_strconcat (dst_repo_name, "/", dst_path, NULL);
json_object_set_string_member (msg, "srcpath", srcpath);
json_object_set_string_member (msg, "dstpath", dstpath);
mq_mgr_push_msg (seaf->mq_mgr, SEADRIVE_NOTIFY_CHAN, msg);
out:
g_free (src_repo_name);
g_free (dst_repo_name);
g_free (srcpath);
g_free (dstpath);
}
static void *
cross_repo_rename_thread (void *vdata)
{
CrossRepoRenameData *data = vdata;
SeafAccount *account = seaf_repo_manager_get_account (seaf->repo_mgr,
data->server,
data->user);
SeafRepo *repo1 = NULL, *repo2 = NULL;
if (!account) {
goto out;
}
notify_cross_repo_move (data->repo_id1, data->oldpath,
data->repo_id2, data->newpath,
TRUE, FALSE);
if (http_tx_manager_api_move_file (seaf->http_tx_mgr,
account->server,
account->token,
data->repo_id1,
data->oldpath,
data->repo_id2,
data->newpath,
data->is_file) < 0) {
seaf_warning ("Failed to move %s/%s to %s/%s.\n",
data->repo_id1, data->oldpath, data->repo_id2, data->newpath);
notify_cross_repo_move (data->repo_id1, data->oldpath,
data->repo_id2, data->newpath,
FALSE, TRUE);
goto out;
}
/* Move files/folders in cache. */
file_cache_mgr_rename (seaf->file_cache_mgr, data->repo_id1, data->oldpath,
data->repo_id2, data->newpath);
/* Trigger repo sync for both repos. */
repo1 = seaf_repo_manager_get_repo (seaf->repo_mgr, data->repo_id1);
if (repo1)
repo1->force_sync_pending = TRUE;
seaf_repo_unref (repo1);
repo2 = seaf_repo_manager_get_repo (seaf->repo_mgr, data->repo_id2);
if (repo2)
repo2->force_sync_pending = TRUE;
seaf_repo_unref (repo2);
notify_cross_repo_move (data->repo_id1, data->oldpath,
data->repo_id2, data->newpath,
FALSE, FALSE);
out:
seaf_account_free (account);
cross_repo_rename_data_free (data);
return NULL;
}
static int
cross_repo_rename (const char *repo_id1, const char *oldpath,
const char *repo_id2, const char *newpath)
{
SeafRepo *repo1 = NULL, *repo2 = NULL;
RepoTreeStat st;
CrossRepoRenameData *data;
pthread_t tid;
int rc;
int ret = 0;
repo1 = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id1);
if (!repo1) {
ret = -ENOENT;
goto out;
}
repo2 = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id2);
if (!repo2) {
ret = -ENOENT;
goto out;
}
if (repo_tree_stat_path (repo1->tree, oldpath, &st) < 0) {
ret = -ENOENT;
goto out;
}
/* TODO: support replacing file/dir by rename. */
if (repo_tree_stat_path (repo2->tree, newpath, &st) == 0) {
ret = -EIO;
goto out;
}
data = g_new0 (CrossRepoRenameData, 1);
data->server = g_strdup (repo1->server);
data->user = g_strdup (repo1->user);
data->repo_id1 = g_strdup(repo_id1);
data->oldpath = g_strdup(oldpath);
data->repo_id2 = g_strdup(repo_id2);
data->newpath = g_strdup(newpath);
data->is_file = S_ISREG(st.mode);
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
rc = pthread_create (&tid, &attr, cross_repo_rename_thread, data);
if (rc != 0) {
cross_repo_rename_data_free (data);
ret = -ENOMEM;
}
out:
seaf_repo_unref (repo1);
seaf_repo_unref (repo2);
return ret;
}
int seadrive_fuse_rename (const char *oldpath, const char *newpath)
{
FusePathComps comps1, comps2;
int ret = 0;
seaf_debug ("rename %s to %s called.\n", oldpath, newpath);
if (!seaf->started)
return 0;
memset (&comps1, 0, sizeof(comps1));
memset (&comps2, 0, sizeof(comps2));
if (parse_fuse_path (oldpath, &comps1) < 0) {
ret = -ENOENT;
goto out;
}
if (parse_fuse_path (newpath, &comps2) < 0) {
if (!comps2.repo_type)
ret = -EACCES;
else
ret = -ENOENT;
goto out;
}
if (!comps1.account_info || !comps2.account_info) {
ret = -EACCES;
goto out;
}
if (g_strcmp0 (comps1.account_info->server, comps2.account_info->server) !=0 ||
g_strcmp0 (comps1.account_info->username, comps2.account_info->username) != 0) {
ret = -EACCES;
goto out;
}
if (!comps1.repo_type || !comps2.repo_type) {
ret = -EACCES;
goto out;
}
/* Category level. */
if (comps1.root_path) {
ret = -ENOENT;
goto out;
}
if (comps2.root_path) {
/* Don't allow moving a folder under a repo to category level (become a new repo). */
if (comps1.repo_path) {
ret = -EACCES;
goto out;
}
/* Don't allow renaming category dir to a repo dir. */
if (!comps1.repo_info) {
ret = -EACCES;
goto out;
}
/* Don't allow moving repos from one category to another;
* Don't allow renaming repos shared to me or from group.
*/
if ((comps1.repo_type != comps2.repo_type) || comps1.repo_type != REPO_TYPE_MINE) {
ret = -EACCES;
goto out;
}
seaf->last_access_fs_time = (gint64)time(NULL);
// Rename repo
ret = seaf_sync_manager_rename_repo (seaf->sync_mgr,
comps1.account_info->server,
comps1.account_info->username,
comps1.repo_info->id,
comps2.root_path);
if (ret < 0)
ret = -EIO;
goto out;
}
if (!comps1.repo_info && !comps1.repo_path) {
/* Category directory */
ret = -EACCES;
goto out;
}
if (!comps2.repo_info && !comps2.repo_path) {
/* Category directory */
ret = -EACCES;
goto out;
}
/* Repo level. */
if (!comps1.repo_path && !comps2.repo_path) {
/* Renaming one repo to another. */
ret = -EEXIST;
goto out;
}
if ((comps1.repo_path && !comps2.repo_path) ||
(!comps1.repo_path && comps2.repo_path)) {
/* Moving repo to sub-folder, or vice versa. */
ret = -EACCES;
goto out;
}
if (!seaf_repo_manager_is_path_writable (seaf->repo_mgr,
comps1.repo_info->id,
comps1.repo_path) ||
!seaf_repo_manager_is_path_writable (seaf->repo_mgr,
comps2.repo_info->id,
comps2.repo_path)) {
ret = -EACCES;
goto out;
}
if (seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,
comps1.repo_info->id,
comps1.repo_path) ||
seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,
comps2.repo_info->id,
comps2.repo_path)) {
ret = -EACCES;
goto out;
}
seaf->last_access_fs_time = (gint64)time(NULL);
if (strcmp (comps1.repo_info->id, comps2.repo_info->id) != 0) {
ret = cross_repo_rename (comps1.repo_info->id, comps1.repo_path,
comps2.repo_info->id, comps2.repo_path);
} else {
ret = repo_rename (comps1.repo_info->id,
comps1.repo_path,
comps2.repo_path);
if (ret == 0) {
seaf_sync_manager_delete_active_path (seaf->sync_mgr,
comps1.repo_info->id,
comps1.repo_path);
}
}
out:
path_comps_free (&comps1);
path_comps_free (&comps2);
return ret;
}
int
seadrive_fuse_open(const char *path, struct fuse_file_info *info)
{
FusePathComps comps;
CachedFileHandle *handle;
int ret = 0;
seaf_debug ("open %s called.\n", path);
memset (&comps, 0, sizeof(comps));
if (parse_fuse_path (path, &comps) < 0) {
return -ENOENT;
}
if (!comps.account_info) {
ret = -EINVAL;
goto out;
} else if (!comps.repo_type) {
ret = -EINVAL;
goto out;
} else if (comps.root_path) {
ret = -EINVAL;
goto out;
} else if (!comps.repo_info && !comps.repo_path) {
/* Root directory */
ret = -EINVAL;
goto out;
} else if (!comps.repo_path) {
/* Repo directory */
ret = -EINVAL;
goto out;
}
if (((info->flags & O_WRONLY) || (info->flags & O_RDWR)) &&
(!seaf_repo_manager_is_path_writable (seaf->repo_mgr,
comps.repo_info->id,
comps.repo_path) ||
seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,
comps.repo_info->id,
comps.repo_path))) {
ret = -EACCES;
goto out;
}
seaf->last_access_fs_time = (gint64)time(NULL);
handle = file_cache_mgr_open (seaf->file_cache_mgr,
comps.repo_info->id, comps.repo_path,
info->flags);
if (!handle) {
ret = -EIO;
goto out;
}
info->fh = (uint64_t)handle;
out:
path_comps_free (&comps);
return ret;
}
int
seadrive_fuse_read(const char *path, char *buf, size_t size,
off_t offset, struct fuse_file_info *info)
{
seaf_debug ("read %s called. size = %"G_GINT64_FORMAT", offset = %"G_GINT64_FORMAT"\n",
path, (gint64)size, (gint64)offset);
if (!info->fh) {
return -EIO;
}
CachedFileHandle *handle = (CachedFileHandle *)info->fh;
seaf->last_access_fs_time = (gint64)time(NULL);
char *repo_id = NULL, *file_path = NULL;
file_cache_mgr_get_file_info_from_handle (seaf->file_cache_mgr, handle,
&repo_id, &file_path);
int ret = 0;
ret = file_cache_mgr_read_by_path (seaf->file_cache_mgr,
repo_id, file_path,
buf, size, offset);
if (ret < 0) {
seaf_warning ("Failed to read %s/%s: %s.\n", repo_id, file_path, strerror(-ret));
}
g_free (repo_id);
g_free (file_path);
return ret;
}
int
seadrive_fuse_write(const char *path, const char *buf, size_t size,
off_t offset, struct fuse_file_info *info)
{
int ret = 0;
CachedFileHandle *handle;
FileCacheStat st;
char *repo_id = NULL, *file_path = NULL;
SeafRepo *repo = NULL;
JournalOp *op;
RepoTreeStat tree_st;
seaf_debug ("write %s called. size = %"G_GINT64_FORMAT", offset = %"G_GINT64_FORMAT"\n",
path, size, offset);
if (!info->fh) {
return -EIO;
}
handle = (CachedFileHandle *)info->fh;
seaf->last_access_fs_time = (gint64)time(NULL);
ret = file_cache_mgr_write (seaf->file_cache_mgr,
handle,
buf, size, offset);
if (ret < 0)
return ret;
if (file_cache_mgr_stat_handle (seaf->file_cache_mgr, handle, &st) < 0) {
return -EIO;
}
file_cache_mgr_get_file_info_from_handle (seaf->file_cache_mgr,
handle,
&repo_id, &file_path);
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo) {
seaf_warning ("Failed to get repo %s.\n", repo_id);
ret = -EIO;
goto out;
}
repo_tree_set_file_mtime (repo->tree, file_path, st.mtime);
repo_tree_set_file_size (repo->tree, file_path, st.size);
if (repo_tree_stat_path (repo->tree, file_path, &tree_st) < 0) {
ret = -EIO;
goto out;
}
op = journal_op_new (OP_TYPE_UPDATE_FILE, file_path, NULL,
st.size, st.mtime, tree_st.mode);
if (journal_append_op (repo->journal, op) < 0) {
seaf_warning ("Failed to append op to journal of repo %s.\n", repo->id);
ret = -ENOMEM;
journal_op_free (op);
}
out:
g_free (repo_id);
g_free (file_path);
seaf_repo_unref (repo);
return ret;
}
int seadrive_fuse_release (const char *path, struct fuse_file_info *info)
{
CachedFileHandle *handle = (CachedFileHandle *)info->fh;
seaf_debug ("release %s called.\n", path);
if (!handle)
return 0;
file_cache_mgr_close_file_handle (handle);
return 0;
}
int
seadrive_fuse_truncate (const char *path, off_t length)
{
FusePathComps comps;
char *repo_id, *file_path;
FileCacheStat st;
SeafRepo *repo = NULL;
JournalOp *op;
RepoTreeStat tree_st;
int ret = 0;
seaf_debug ("truncate %s called, len = %"G_GINT64_FORMAT".\n", path, length);
memset (&comps, 0, sizeof(comps));
if (parse_fuse_path (path, &comps) < 0) {
return -ENOENT;
}
if (!comps.account_info) {
ret = -EINVAL;
goto out;
} else if (!comps.repo_type) {
ret = -EINVAL;
goto out;
} else if (comps.root_path) {
ret = -EINVAL;
goto out;
} else if (!comps.repo_info && !comps.repo_path) {
/* Root directory */
ret = -EINVAL;
goto out;
} else if (!comps.repo_path) {
/* Repo directory */
ret = -EINVAL;
goto out;
}
if (!seaf_repo_manager_is_path_writable (seaf->repo_mgr,
comps.repo_info->id,
comps.repo_path) ||
seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,
comps.repo_info->id,
comps.repo_path)) {
ret = -EACCES;
goto out;
}
seaf->last_access_fs_time = (gint64)time(NULL);
repo_id = comps.repo_info->id;
file_path = comps.repo_path;
gboolean not_cached = FALSE;
ret = file_cache_mgr_truncate (seaf->file_cache_mgr,
repo_id, file_path,
length, ¬_cached);
if (ret < 0) {
goto out;
}
if (not_cached) {
st.mtime = (gint64)time(NULL);
st.size = 0;
} else {
if (file_cache_mgr_stat (seaf->file_cache_mgr, repo_id, file_path, &st) < 0) {
ret = -EIO;
goto out;
}
}
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo) {
seaf_warning ("Failed to get repo %s.\n", repo_id);
ret = -EIO;
goto out;
}
repo_tree_set_file_mtime (repo->tree, file_path, st.mtime);
repo_tree_set_file_size (repo->tree, file_path, st.size);
if (repo_tree_stat_path (repo->tree, file_path, &tree_st) < 0) {
ret = -EIO;
goto out;
}
op = journal_op_new (OP_TYPE_UPDATE_FILE, file_path, NULL,
st.size, st.mtime, tree_st.mode);
if (journal_append_op (repo->journal, op) < 0) {
seaf_warning ("Failed to append op to journal of repo %s.\n", repo->id);
ret = -ENOMEM;
journal_op_free (op);
}
out:
path_comps_free (&comps);
seaf_repo_unref (repo);
return ret;
}
int
seadrive_fuse_statfs (const char *path, struct statvfs *buf)
{
SeafAccountSpace *space = NULL;
gint64 total, used;
GList *accounts = NULL, *ptr;
SeafAccount *account;
accounts = seaf_repo_manager_get_account_list (seaf->repo_mgr);
if (!accounts) {
total = 0;
used = 0;
}
for (ptr = accounts; ptr; ptr = ptr->next) {
account = ptr->data;
space = seaf_repo_manager_get_account_space (seaf->repo_mgr,
account->server,
account->username);
total += space->total;
used += space->used;
}
if (accounts)
g_list_free_full (accounts, (GDestroyNotify)seaf_account_free);
buf->f_namemax = 255;
buf->f_bsize = 4096;
/*
* df seems to use f_bsize instead of f_frsize, so make them
* the same
*/
buf->f_frsize = buf->f_bsize;
buf->f_blocks = total / buf->f_frsize;
buf->f_bfree = buf->f_bavail = (total - used) / buf->f_frsize;
buf->f_files = buf->f_ffree = 1000000000;
g_free (space);
return 0;
}
int
seadrive_fuse_chmod (const char *path, mode_t mode)
{
FusePathComps comps;
seaf_debug ("chmod %s called. mode = %o.\n", path, mode);
memset (&comps, 0, sizeof(comps));
if (parse_fuse_path (path, &comps) < 0) {
return -ENOENT;
}
return 0;
}
int
seadrive_fuse_utimens (const char *path, const struct timespec tv[2])
{
RepoTreeStat tree_st;
char *repo_id = NULL, *file_path = NULL;
SeafRepo *repo = NULL;
JournalOp *op = NULL;
int ret = 0;
FusePathComps comps;
time_t atime = tv[0].tv_sec;
time_t mtime = tv[1].tv_sec;
seaf_debug ("utimens %s called. mtime = %"G_GINT64_FORMAT"\n", path, mtime);
memset (&comps, 0, sizeof(comps));
if (parse_fuse_path (path, &comps) < 0) {
return -ENOENT;
}
if (!comps.account_info) {
goto out;
}
if (comps.root_path != NULL ||
!comps.repo_type ||
!comps.repo_info ||
!comps.repo_path)
goto out;
repo_id = comps.repo_info->id;
file_path = comps.repo_path;
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo) {
seaf_warning ("Failed to get repo %s.\n", repo_id);
ret = -ENOENT;
goto out;
}
ret = file_cache_mgr_utimen (seaf->file_cache_mgr, repo_id, file_path, mtime, atime);
if (ret < 0)
goto out;
if (repo_tree_stat_path (repo->tree, file_path, &tree_st) < 0) {
ret = -ENOENT;
goto out;
}
if (tree_st.mtime == mtime)
goto out;
repo_tree_set_file_mtime (repo->tree, file_path, mtime);
op = journal_op_new (OP_TYPE_UPDATE_ATTR, file_path, NULL,
tree_st.size, mtime, tree_st.mode);
if (journal_append_op (repo->journal, op) < 0) {
seaf_warning ("Failed to append op to journal of repo %s.\n", repo->id);
ret = -ENOMEM;
journal_op_free (op);
}
out:
path_comps_free (&comps);
seaf_repo_unref (repo);
return ret;
}
int
seadrive_fuse_symlink (const char *from, const char *to)
{
FusePathComps comps_from, comps_to;
seaf_debug ("symlink %s %s called.\n", from, to);
memset (&comps_from, 0, sizeof(comps_from));
memset (&comps_to, 0, sizeof(comps_to));
if (parse_fuse_path (from, &comps_from) < 0) {
return -ENOENT;
}
if (parse_fuse_path (to, &comps_to) < 0) {
return -ENOENT;
}
return 0;
}
int
seadrive_fuse_setxattr (const char *path, const char *name, const char *value,
size_t size, int flags)
{
FusePathComps comps;
seaf_debug ("setxattr: %s %s\n", path, name);
memset (&comps, 0, sizeof(comps));
if (parse_fuse_path (path, &comps) < 0) {
return -ENOENT;
}
return 0;
}
#ifdef __APPLE__
int
seadrive_fuse_getxattr (const char *path, const char *name, char *value, size_t size,
uint32_t position)
#else
int
seadrive_fuse_getxattr (const char *path, const char *name, char *value, size_t size)
#endif
{
FusePathComps comps;
seaf_debug ("getxattr: %s %s\n", path, name);
memset (&comps, 0, sizeof(comps));
if (parse_fuse_path (path, &comps) < 0) {
return -ENOENT;
}
return -ENODATA;
}
int
seadrive_fuse_listxattr (const char *path, char *list, size_t size)
{
seaf_debug ("listxattr: %s\n", path);
return 0;
}
int
seadrive_fuse_removexattr (const char *path, const char *name)
{
seaf_debug ("removexattr: %s %s\n", path, name);
return 0;
}
#endif // __linux__
seadrive-fuse-3.0.13/src/fuse-ops.h 0000664 0000000 0000000 00000004226 14761776747 0017077 0 ustar 00root root 0000000 0000000 #ifndef SEADRIVE_FUSE_OPS_H
#define SEADRIVE_FUSE_OPS_H
#if defined __linux__ || defined __APPLE__
#include
int seadrive_fuse_getattr(const char *path, struct stat *stbuf);
int seadrive_fuse_readdir(const char *path, void *buf,
fuse_fill_dir_t filler, off_t offset,
struct fuse_file_info *info);
int seadrive_fuse_mknod (const char *path, mode_t mode, dev_t dev);
int seadrive_fuse_mkdir (const char *path, mode_t mode);
int seadrive_fuse_unlink (const char *path);
int seadrive_fuse_rmdir (const char *path);
int seadrive_fuse_rename (const char *oldpath, const char *newpath);
int seadrive_fuse_open (const char *path, struct fuse_file_info *info);
int seadrive_fuse_read (const char *path, char *buf, size_t size,
off_t offset, struct fuse_file_info *info);
int seadrive_fuse_write (const char *path, const char *buf, size_t size,
off_t offset, struct fuse_file_info *info);
int seadrive_fuse_release (const char *path, struct fuse_file_info *fi);
int seadrive_fuse_truncate (const char *path, off_t length);
int seadrive_fuse_statfs (const char *path, struct statvfs *buf);
int seadrive_fuse_chmod (const char *path, mode_t mode);
int seadrive_fuse_utimens (const char *, const struct timespec tv[2]);
int seadrive_fuse_symlink (const char *from, const char *to);
int seadrive_fuse_link (const char *from, const char *to);
#ifdef __APPLE__
int seadrive_fuse_setxattr (const char *path, const char *name, const char *value,
size_t size, int flags, uint32_t position);
#else
int seadrive_fuse_setxattr (const char *path, const char *name, const char *value,
size_t size, int flags);
#endif
#ifdef __APPLE__
int seadrive_fuse_getxattr (const char *path, const char *name, char *value, size_t size,
uint32_t position);
#else
int seadrive_fuse_getxattr (const char *path, const char *name, char *value, size_t size);
#endif
int seadrive_fuse_listxattr (const char *path, char *list, size_t size);
int seadrive_fuse_removexattr (const char *path, const char *name);
#endif // __linux__
#endif
seadrive-fuse-3.0.13/src/http-tx-mgr.c 0000664 0000000 0000000 00000563026 14761776747 0017534 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#include "common.h"
#include
#include
#include
#ifndef USE_GPL_CRYPTO
#include
#include
#include
#include
#endif
#include "seafile-config.h"
#include "seafile-session.h"
#include "http-tx-mgr.h"
#include "seafile-error.h"
#include "utils.h"
#include "diff-simple.h"
#include "job-mgr.h"
#include "timer.h"
#define DEBUG_FLAG SEAFILE_DEBUG_TRANSFER
#include "log.h"
#define HTTP_OK 200
#define HTTP_RES_PARTIAL 206
#define HTTP_MOVED_PERMANENTLY 301
#define HTTP_BAD_REQUEST 400
#define HTTP_UNAUTHORIZED 401
#define HTTP_FORBIDDEN 403
#define HTTP_NOT_FOUND 404
#define HTTP_REQUEST_TIME_OUT 408
#define HTTP_NO_QUOTA 443
#define HTTP_REPO_DELETED 444
#define HTTP_REPO_CORRUPTED 445
#define HTTP_BLOCK_MISSING 446
#define HTTP_REPO_TOO_LARGE 447
#define HTTP_INTERNAL_SERVER_ERROR 500
#define RESET_BYTES_INTERVAL_MSEC 1000
#define CLEAR_POOL_ERR_CNT 3
#ifndef SEAFILE_CLIENT_VERSION
#define SEAFILE_CLIENT_VERSION PACKAGE_VERSION
#endif
#ifdef __APPLE__
#define USER_AGENT_OS "Apple OS X"
#endif
#ifdef __linux__
#define USER_AGENT_OS "Linux"
#endif
struct _Connection {
CURL *curl;
gint64 ctime; /* Used to clean up unused connection. */
gboolean release; /* If TRUE, the connection will be released. */
};
typedef struct _Connection Connection;
struct _ConnectionPool {
char *host;
GQueue *queue;
pthread_mutex_t lock;
int err_cnt;
};
typedef struct _ConnectionPool ConnectionPool;
struct _HttpTxPriv {
GHashTable *download_tasks;
GHashTable *upload_tasks;
GHashTable *connection_pools; /* host -> connection pool */
pthread_mutex_t pools_lock;
SeafTimer *reset_bytes_timer;
char *env_ca_bundle_path;
// Uploaded file count
gint uploaded;
// Upload failed count
gint upload_err;
// Total file count need to upload
gint total_upload;
// Uploading files: file_path <-> FileUploadProgress
GHashTable *uploading_files;
// Uploaded files
GQueue *uploaded_files;
pthread_mutex_t progress_lock;
/* Regex to parse error message returned by update-branch. */
GRegex *locked_error_regex;
GRegex *folder_perm_error_regex;
GRegex *too_many_files_error_regex;
};
typedef struct _HttpTxPriv HttpTxPriv;
/* Http Tx Task */
static HttpTxTask *
http_tx_task_new (HttpTxManager *mgr,
const char *repo_id,
int repo_version,
int type,
gboolean is_clone,
const char *host,
const char *token)
{
HttpTxTask *task = g_new0 (HttpTxTask, 1);
task->manager = mgr;
memcpy (task->repo_id, repo_id, 36);
task->repo_version = repo_version;
task->type = type;
task->is_clone = is_clone;
task->host = g_strdup(host);
task->token = g_strdup(token);
return task;
}
static void
http_tx_task_free (HttpTxTask *task)
{
if (task->repo_uname)
g_free (task->repo_uname);
g_free (task->unsyncable_path);
g_free (task->host);
g_free (task->token);
g_free (task->server);
g_free (task->user);
g_free (task);
}
static const char *http_task_state_str[] = {
"normal",
"canceled",
"finished",
"error",
};
static const char *http_task_rt_state_str[] = {
"init",
"check",
"commit",
"fs",
"data",
"update-branch",
"finished",
};
static const char *http_task_error_strs[] = {
"Successful",
"Permission denied on server",
"Network error",
"Cannot resolve proxy address",
"Cannot resolve server address",
"Cannot connect to server",
"Failed to establish secure connection",
"Data transfer was interrupted",
"Data transfer timed out",
"Unhandled http redirect from server",
"Server error",
"Bad request",
"Internal data corrupt on the client",
"Not enough memory",
"Failed to write data on the client",
"Storage quota full",
"Files are locked by other application",
"Library deleted on server",
"Library damaged on server",
"Unknown error",
};
/* Http connection and connection pool. */
static Connection *
connection_new ()
{
Connection *conn = g_new0 (Connection, 1);
conn->curl = curl_easy_init();
conn->ctime = (gint64)time(NULL);
return conn;
}
static void
connection_free (Connection *conn)
{
curl_easy_cleanup (conn->curl);
g_free (conn);
}
static ConnectionPool *
connection_pool_new (const char *host)
{
ConnectionPool *pool = g_new0 (ConnectionPool, 1);
pool->host = g_strdup(host);
pool->queue = g_queue_new ();
pthread_mutex_init (&pool->lock, NULL);
return pool;
}
static ConnectionPool *
find_connection_pool (HttpTxPriv *priv, const char *host)
{
ConnectionPool *pool;
pthread_mutex_lock (&priv->pools_lock);
pool = g_hash_table_lookup (priv->connection_pools, host);
if (!pool) {
pool = connection_pool_new (host);
g_hash_table_insert (priv->connection_pools, g_strdup(host), pool);
}
pthread_mutex_unlock (&priv->pools_lock);
return pool;
}
static Connection *
connection_pool_get_connection (ConnectionPool *pool)
{
Connection *conn = NULL;
pthread_mutex_lock (&pool->lock);
conn = g_queue_pop_head (pool->queue);
if (!conn) {
conn = connection_new ();
}
pthread_mutex_unlock (&pool->lock);
return conn;
}
static void
connection_pool_clear (ConnectionPool *pool)
{
Connection *conn = NULL;
while (1) {
conn = g_queue_pop_head (pool->queue);
if (!conn)
break;
connection_free (conn);
}
}
static void
connection_pool_return_connection (ConnectionPool *pool, Connection *conn)
{
if (!conn)
return;
if (conn->release) {
connection_free (conn);
pthread_mutex_lock (&pool->lock);
if (++pool->err_cnt >= CLEAR_POOL_ERR_CNT) {
connection_pool_clear (pool);
}
pthread_mutex_unlock (&pool->lock);
return;
}
curl_easy_reset (conn->curl);
/* Reset error count when one connection succeeded. */
pthread_mutex_lock (&pool->lock);
pool->err_cnt = 0;
g_queue_push_tail (pool->queue, conn);
pthread_mutex_unlock (&pool->lock);
}
#define LOCKED_ERROR_PATTERN "File (.+) is locked"
#define FOLDER_PERM_ERROR_PATTERN "Update to path (.+) is not allowed by folder permission settings"
#define TOO_MANY_FILES_ERROR_PATTERN "Too many files in library"
HttpTxManager *
http_tx_manager_new (struct _SeafileSession *seaf)
{
HttpTxManager *mgr = g_new0 (HttpTxManager, 1);
HttpTxPriv *priv = g_new0 (HttpTxPriv, 1);
const char *env_ca_path = NULL;
mgr->seaf = seaf;
priv->download_tasks = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free,
(GDestroyNotify)http_tx_task_free);
priv->upload_tasks = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free,
(GDestroyNotify)http_tx_task_free);
priv->connection_pools = g_hash_table_new (g_str_hash, g_str_equal);
pthread_mutex_init (&priv->pools_lock, NULL);
env_ca_path = g_getenv("SEAFILE_SSL_CA_PATH");
if (env_ca_path)
priv->env_ca_bundle_path = g_strdup (env_ca_path);
priv->uploading_files = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
priv->uploaded_files = g_queue_new ();
pthread_mutex_init (&priv->progress_lock, NULL);
GError *error = NULL;
priv->locked_error_regex = g_regex_new (LOCKED_ERROR_PATTERN, 0, 0, &error);
if (error) {
seaf_warning ("Failed to create regex '%s': %s\n", LOCKED_ERROR_PATTERN, error->message);
g_clear_error (&error);
}
priv->folder_perm_error_regex = g_regex_new (FOLDER_PERM_ERROR_PATTERN, 0, 0, &error);
if (error) {
seaf_warning ("Failed to create regex '%s': %s\n", FOLDER_PERM_ERROR_PATTERN, error->message);
g_clear_error (&error);
}
priv->too_many_files_error_regex = g_regex_new (TOO_MANY_FILES_ERROR_PATTERN, 0, 0, &error);
if (error) {
seaf_warning ("Failed to create regex '%s': %s\n", TOO_MANY_FILES_ERROR_PATTERN, error->message);
g_clear_error (&error);
}
mgr->priv = priv;
return mgr;
}
static int
reset_bytes (void *vdata)
{
HttpTxManager *mgr = vdata;
HttpTxPriv *priv = mgr->priv;
GHashTableIter iter;
gpointer key, value;
HttpTxTask *task;
g_hash_table_iter_init (&iter, priv->download_tasks);
while (g_hash_table_iter_next (&iter, &key, &value)) {
task = value;
task->last_tx_bytes = g_atomic_int_get (&task->tx_bytes);
g_atomic_int_set (&task->tx_bytes, 0);
}
g_hash_table_iter_init (&iter, priv->upload_tasks);
while (g_hash_table_iter_next (&iter, &key, &value)) {
task = value;
task->last_tx_bytes = g_atomic_int_get (&task->tx_bytes);
g_atomic_int_set (&task->tx_bytes, 0);
}
return 1;
}
/* static void * */
/* save_uploaded_file_list_worker (void *data); */
/* static void */
/* load_uploaded_file_list (HttpTxManager *mgr); */
int
http_tx_manager_start (HttpTxManager *mgr)
{
/* TODO: add a timer to clean up unused Http connections. */
mgr->priv->reset_bytes_timer = seaf_timer_new (reset_bytes,
mgr,
RESET_BYTES_INTERVAL_MSEC);
/* load_uploaded_file_list (mgr); */
/* pthread_t tid; */
/* int rc; */
/* rc = pthread_create (&tid, NULL, save_uploaded_file_list_worker, mgr); */
/* if (rc != 0) { */
/* seaf_warning ("Failed to create save uploaded files worker thread.\n"); */
/* } */
return 0;
}
/* Common Utility Functions. */
#ifndef USE_GPL_CRYPTO
char *ca_paths[] = {
"/etc/ssl/certs/ca-certificates.crt",
"/etc/ssl/certs/ca-bundle.crt",
"/etc/pki/tls/certs/ca-bundle.crt",
"/usr/share/ssl/certs/ca-bundle.crt",
"/usr/local/share/certs/ca-root-nss.crt",
"/etc/ssl/cert.pem",
};
static void
load_ca_bundle(CURL *curl)
{
const char *env_ca_path = seaf->http_tx_mgr->priv->env_ca_bundle_path;
int i;
const char *ca_path;
gboolean found = FALSE;
for (i = 0; i < sizeof(ca_paths) / sizeof(ca_paths[0]); i++) {
ca_path = ca_paths[i];
if (seaf_util_exists (ca_path)) {
found = TRUE;
break;
}
}
if (env_ca_path) {
if (seaf_util_exists (env_ca_path)) {
curl_easy_setopt (curl, CURLOPT_CAINFO, env_ca_path);
return;
}
}
if (found)
curl_easy_setopt (curl, CURLOPT_CAINFO, ca_path);
}
#endif /* USE_GPL_CRYPTO */
static void
set_proxy (CURL *curl, gboolean is_https)
{
/* Disable proxy if proxy options are not set properly. */
if (!seaf->use_http_proxy || !seaf->http_proxy_type || !seaf->http_proxy_addr) {
curl_easy_setopt (curl, CURLOPT_PROXY, NULL);
return;
}
if (g_strcmp0(seaf->http_proxy_type, PROXY_TYPE_HTTP) == 0) {
curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
/* Use CONNECT method create a SSL tunnel if https is used. */
if (is_https)
curl_easy_setopt(curl, CURLOPT_HTTPPROXYTUNNEL, 1L);
curl_easy_setopt(curl, CURLOPT_PROXY, seaf->http_proxy_addr);
curl_easy_setopt(curl, CURLOPT_PROXYPORT,
seaf->http_proxy_port > 0 ? seaf->http_proxy_port : 80);
if (seaf->http_proxy_username && seaf->http_proxy_password) {
curl_easy_setopt(curl, CURLOPT_PROXYAUTH,
CURLAUTH_BASIC |
CURLAUTH_DIGEST |
CURLAUTH_DIGEST_IE |
CURLAUTH_GSSNEGOTIATE |
CURLAUTH_NTLM);
curl_easy_setopt(curl, CURLOPT_PROXYUSERNAME, seaf->http_proxy_username);
curl_easy_setopt(curl, CURLOPT_PROXYPASSWORD, seaf->http_proxy_password);
}
} else if (g_strcmp0(seaf->http_proxy_type, PROXY_TYPE_SOCKS) == 0) {
if (seaf->http_proxy_port < 0)
return;
curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME);
curl_easy_setopt(curl, CURLOPT_PROXY, seaf->http_proxy_addr);
curl_easy_setopt(curl, CURLOPT_PROXYPORT, seaf->http_proxy_port);
if (seaf->http_proxy_username && g_strcmp0 (seaf->http_proxy_username, "") != 0)
curl_easy_setopt(curl, CURLOPT_PROXYUSERNAME, seaf->http_proxy_username);
if (seaf->http_proxy_password && g_strcmp0 (seaf->http_proxy_password, "") != 0)
curl_easy_setopt(curl, CURLOPT_PROXYPASSWORD, seaf->http_proxy_password);
}
}
typedef struct _HttpResponse {
char *content;
size_t size;
} HttpResponse;
static size_t
recv_response (void *contents, size_t size, size_t nmemb, void *userp)
{
size_t realsize = size * nmemb;
HttpResponse *rsp = userp;
rsp->content = g_realloc (rsp->content, rsp->size + realsize);
if (!rsp->content) {
seaf_warning ("Not enough memory.\n");
/* return a value other than realsize to signify an error. */
return 0;
}
memcpy (rsp->content + rsp->size, contents, realsize);
rsp->size += realsize;
return realsize;
}
/* 5 minutes timeout for background requests. */
#define HTTP_TIMEOUT_SEC 300
/* 5 seconds timeout for foreground requests that can impact UI response time. */
#define REPO_OPER_TIMEOUT 5
/*
* The @timeout parameter is for detecting network connection problems.
* The @timeout parameter should be set to TRUE for data-transfer-only operations,
* such as getting objects, blocks. For operations that requires calculations
* on the server side, the timeout should be set to FALSE. Otherwise when
* the server sometimes takes more than 45 seconds to calculate the result,
* the client will time out.
*/
static int
http_get_common (CURL *curl, const char *url,
int *rsp_status, char **rsp_content, gint64 *rsp_size,
HttpRecvCallback callback, void *cb_data,
gboolean timeout, int timeout_sec,
int *pcurl_error)
{
int ret = 0;
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
if (seafile_debug_flag_is_set (SEAFILE_DEBUG_CURL)) {
curl_easy_setopt (curl, CURLOPT_VERBOSE, 1);
curl_easy_setopt (curl, CURLOPT_STDERR, seafile_get_logfp());
}
if (timeout) {
/* Set low speed limit to 1 bytes. This effectively means no data. */
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1);
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, timeout_sec);
}
#ifndef USE_GPL_CRYPTO
if (seaf->disable_verify_certificate) {
curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, 0L);
}
#endif
HttpResponse rsp;
memset (&rsp, 0, sizeof(rsp));
if (rsp_content) {
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, recv_response);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &rsp);
} else if (callback) {
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, cb_data);
}
gboolean is_https = (strncasecmp(url, "https", strlen("https")) == 0);
set_proxy (curl, is_https);
#ifndef USE_GPL_CRYPTO
load_ca_bundle (curl);
#endif
int rc = curl_easy_perform (curl);
if (rc != 0) {
if (rc != CURLE_WRITE_ERROR)
seaf_warning ("libcurl failed to GET %s: %s.\n",
url, curl_easy_strerror(rc));
if (pcurl_error)
*pcurl_error = rc;
ret = -1;
goto out;
}
long status;
rc = curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &status);
if (rc != CURLE_OK) {
seaf_warning ("Failed to get status code for GET %s.\n", url);
ret = -1;
goto out;
}
*rsp_status = status;
if (rsp_content) {
*rsp_content = rsp.content;
*rsp_size = rsp.size;
}
out:
if (ret < 0)
g_free (rsp.content);
return ret;
}
static int
http_get (CURL *curl, const char *url, const char *token,
int *rsp_status, char **rsp_content, gint64 *rsp_size,
HttpRecvCallback callback, void *cb_data,
gboolean timeout, int timeout_sec,
int *pcurl_error)
{
char *token_header;
struct curl_slist *headers = NULL;
int ret;
headers = curl_slist_append (headers, "User-Agent: SeaDrive/"SEAFILE_CLIENT_VERSION" ("USER_AGENT_OS")");
if (token) {
token_header = g_strdup_printf ("Seafile-Repo-Token: %s", token);
headers = curl_slist_append (headers, token_header);
g_free (token_header);
token_header = g_strdup_printf ("Authorization: Token %s", token);
headers = curl_slist_append (headers, token_header);
g_free (token_header);
}
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
ret = http_get_common (curl, url, rsp_status, rsp_content, rsp_size,
callback, cb_data, timeout, timeout_sec, pcurl_error);
curl_slist_free_all (headers);
return ret;
}
static int
http_get_range (CURL *curl, const char *url, const char *token,
int *rsp_status, char **rsp_content, gint64 *rsp_size,
HttpRecvCallback callback, void *cb_data,
gboolean timeout, int timeout_sec,
guint64 start, guint64 end)
{
char *token_header, *range_header;
struct curl_slist *headers = NULL;
int ret;
headers = curl_slist_append (headers, "User-Agent: SeaDrive/"SEAFILE_CLIENT_VERSION" ("USER_AGENT_OS")");
if (token) {
token_header = g_strdup_printf ("Seafile-Repo-Token: %s", token);
headers = curl_slist_append (headers, token_header);
g_free (token_header);
token_header = g_strdup_printf ("Authorization: Token %s", token);
headers = curl_slist_append (headers, token_header);
g_free (token_header);
}
range_header = g_strdup_printf ("Range: bytes=%"G_GUINT64_FORMAT"-%"G_GUINT64_FORMAT,
start, end);
headers = curl_slist_append (headers, range_header);
g_free (range_header);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
ret = http_get_common (curl, url, rsp_status, rsp_content, rsp_size,
callback, cb_data, timeout, timeout_sec, NULL);
curl_slist_free_all (headers);
return ret;
}
static int
http_api_get (CURL *curl, const char *url, const char *token,
int *rsp_status, char **rsp_content, gint64 *rsp_size,
HttpRecvCallback callback, void *cb_data,
gboolean timeout, int timeout_sec,
int *pcurl_error)
{
char *token_header;
struct curl_slist *headers = NULL;
int ret;
headers = curl_slist_append (headers, "User-Agent: SeaDrive/"SEAFILE_CLIENT_VERSION" ("USER_AGENT_OS")");
if (token) {
token_header = g_strdup_printf ("Authorization: Token %s", token);
headers = curl_slist_append (headers, token_header);
g_free (token_header);
}
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
ret = http_get_common (curl, url, rsp_status, rsp_content, rsp_size,
callback, cb_data, timeout, timeout_sec, pcurl_error);
curl_slist_free_all (headers);
return ret;
}
typedef struct _HttpRequest {
const char *content;
size_t size;
} HttpRequest;
static size_t
send_request (void *ptr, size_t size, size_t nmemb, void *userp)
{
size_t realsize = size *nmemb;
size_t copy_size;
HttpRequest *req = userp;
if (req->size == 0)
return 0;
copy_size = MIN(req->size, realsize);
memcpy (ptr, req->content, copy_size);
req->size -= copy_size;
req->content = req->content + copy_size;
return copy_size;
}
typedef size_t (*HttpSendCallback) (void *, size_t, size_t, void *);
static int
http_put (CURL *curl, const char *url, const char *token,
const char *req_content, gint64 req_size,
HttpSendCallback callback, void *cb_data,
int *rsp_status, char **rsp_content, gint64 *rsp_size,
gboolean timeout,
int *pcurl_error)
{
char *token_header;
struct curl_slist *headers = NULL;
int ret = 0;
if (seafile_debug_flag_is_set (SEAFILE_DEBUG_CURL)) {
curl_easy_setopt (curl, CURLOPT_VERBOSE, 1);
curl_easy_setopt (curl, CURLOPT_STDERR, seafile_get_logfp());
}
headers = curl_slist_append (headers, "User-Agent: SeaDrive/"SEAFILE_CLIENT_VERSION" ("USER_AGENT_OS")");
/* Disable the default "Expect: 100-continue" header */
headers = curl_slist_append (headers, "Expect:");
if (req_content)
headers = curl_slist_append (headers, "Content-Type: application/octet-stream");
if (token) {
token_header = g_strdup_printf ("Seafile-Repo-Token: %s", token);
headers = curl_slist_append (headers, token_header);
g_free (token_header);
token_header = g_strdup_printf ("Authorization: Token %s", token);
headers = curl_slist_append (headers, token_header);
g_free (token_header);
}
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
if (timeout) {
/* Set low speed limit to 1 bytes. This effectively means no data. */
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1);
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, HTTP_TIMEOUT_SEC);
}
#ifndef USE_GPL_CRYPTO
if (seaf->disable_verify_certificate) {
curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, 0L);
}
#endif
HttpRequest req;
if (req_content) {
memset (&req, 0, sizeof(req));
req.content = req_content;
req.size = req_size;
curl_easy_setopt(curl, CURLOPT_READFUNCTION, send_request);
curl_easy_setopt(curl, CURLOPT_READDATA, &req);
curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)req_size);
} else if (callback != NULL) {
curl_easy_setopt(curl, CURLOPT_READFUNCTION, callback);
curl_easy_setopt(curl, CURLOPT_READDATA, cb_data);
curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)req_size);
} else {
curl_easy_setopt (curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)0);
}
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
HttpResponse rsp;
memset (&rsp, 0, sizeof(rsp));
if (rsp_content) {
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, recv_response);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &rsp);
}
gboolean is_https = (strncasecmp(url, "https", strlen("https")) == 0);
set_proxy (curl, is_https);
#ifndef USE_GPL_CRYPTO
load_ca_bundle (curl);
#endif
int rc = curl_easy_perform (curl);
if (rc != 0) {
seaf_warning ("libcurl failed to PUT %s: %s.\n",
url, curl_easy_strerror(rc));
if (pcurl_error)
*pcurl_error = rc;
ret = -1;
goto out;
}
long status;
rc = curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &status);
if (rc != CURLE_OK) {
seaf_warning ("Failed to get status code for PUT %s.\n", url);
ret = -1;
goto out;
}
*rsp_status = status;
if (rsp_content) {
*rsp_content = rsp.content;
*rsp_size = rsp.size;
}
out:
if (ret < 0)
g_free (rsp.content);
curl_slist_free_all (headers);
return ret;
}
static int
http_post_common (CURL *curl, const char *url,
const char *req_content, gint64 req_size,
int *rsp_status, char **rsp_content, gint64 *rsp_size,
gboolean timeout, int timeout_sec,
int *pcurl_error)
{
int ret = 0;
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_POST, 1L);
if (seafile_debug_flag_is_set (SEAFILE_DEBUG_CURL)) {
curl_easy_setopt (curl, CURLOPT_VERBOSE, 1);
curl_easy_setopt (curl, CURLOPT_STDERR, seafile_get_logfp());
}
if (timeout) {
/* Set low speed limit to 1 bytes. This effectively means no data. */
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1);
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, timeout_sec);
}
#ifndef USE_GPL_CRYPTO
if (seaf->disable_verify_certificate) {
curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, 0L);
}
#endif
HttpRequest req;
memset (&req, 0, sizeof(req));
req.content = req_content;
req.size = req_size;
curl_easy_setopt(curl, CURLOPT_READFUNCTION, send_request);
curl_easy_setopt(curl, CURLOPT_READDATA, &req);
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)req_size);
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
HttpResponse rsp;
memset (&rsp, 0, sizeof(rsp));
if (rsp_content) {
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, recv_response);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &rsp);
}
#ifndef USE_GPL_CRYPTO
load_ca_bundle (curl);
#endif
gboolean is_https = (strncasecmp(url, "https", strlen("https")) == 0);
set_proxy (curl, is_https);
int rc = curl_easy_perform (curl);
if (rc != 0) {
seaf_warning ("libcurl failed to POST %s: %s.\n",
url, curl_easy_strerror(rc));
if (pcurl_error)
*pcurl_error = rc;
ret = -1;
goto out;
}
long status;
rc = curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &status);
if (rc != CURLE_OK) {
seaf_warning ("Failed to get status code for POST %s.\n", url);
ret = -1;
goto out;
}
*rsp_status = status;
if (rsp_content) {
*rsp_content = rsp.content;
*rsp_size = rsp.size;
}
out:
if (ret < 0)
g_free (rsp.content);
return ret;
}
static int
http_post (CURL *curl, const char *url, const char *token,
const char *req_content, gint64 req_size,
int *rsp_status, char **rsp_content, gint64 *rsp_size,
gboolean timeout, int timeout_sec,
int *pcurl_error)
{
char *token_header;
struct curl_slist *headers = NULL;
int ret;
g_return_val_if_fail (req_content != NULL, -1);
headers = curl_slist_append (headers, "User-Agent: SeaDrive/"SEAFILE_CLIENT_VERSION" ("USER_AGENT_OS")");
/* Disable the default "Expect: 100-continue" header */
headers = curl_slist_append (headers, "Expect:");
if (req_content) {
json_t *is_json = NULL;
is_json = json_loads (req_content, 0, NULL);
if (is_json) {
headers = curl_slist_append (headers, "Content-Type: application/json");
json_decref (is_json);
} else
headers = curl_slist_append (headers, "Content-Type: application/octet-stream");
}
if (token) {
token_header = g_strdup_printf ("Seafile-Repo-Token: %s", token);
headers = curl_slist_append (headers, token_header);
g_free (token_header);
token_header = g_strdup_printf ("Authorization: Token %s", token);
headers = curl_slist_append (headers, token_header);
g_free (token_header);
}
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
ret = http_post_common (curl, url, req_content, req_size,
rsp_status, rsp_content, rsp_size,
timeout, timeout_sec, pcurl_error);
curl_slist_free_all (headers);
return ret;
}
static int
http_api_post_common (CURL *curl, const char *url, const char *token,
const char *header,
const char *req_content, gint64 req_size,
int *rsp_status, char **rsp_content, gint64 *rsp_size,
gboolean timeout, int timeout_sec,
int *pcurl_error)
{
char *token_header;
struct curl_slist *headers = NULL;
int ret;
g_return_val_if_fail (req_content != NULL, -1);
headers = curl_slist_append (headers, "User-Agent: SeaDrive/"SEAFILE_CLIENT_VERSION" ("USER_AGENT_OS")");
/* Disable the default "Expect: 100-continue" header */
headers = curl_slist_append (headers, "Expect:");
if (req_content)
headers = curl_slist_append (headers, header);
if (token) {
token_header = g_strdup_printf ("Authorization: Token %s", token);
headers = curl_slist_append (headers, token_header);
g_free (token_header);
}
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
ret = http_post_common (curl, url, req_content, req_size,
rsp_status, rsp_content, rsp_size,
timeout, timeout_sec, pcurl_error);
curl_slist_free_all (headers);
return ret;
}
static int
http_api_post (CURL *curl, const char *url, const char *token,
const char *req_content, gint64 req_size,
int *rsp_status, char **rsp_content, gint64 *rsp_size,
gboolean timeout, int timeout_sec,
int *pcurl_error)
{
int ret;
char *header = "Content-Type: application/x-www-form-urlencoded";
ret = http_api_post_common (curl, url, token, header, req_content, req_size, rsp_status,
rsp_content, rsp_size, timeout, timeout_sec, pcurl_error);
return ret;
}
static int
http_api_json_post (CURL *curl, const char *url, const char *token,
const char *req_content, gint64 req_size,
int *rsp_status, char **rsp_content, gint64 *rsp_size,
gboolean timeout, int timeout_sec,
int *pcurl_error)
{
int ret;
char *header = "Content-Type: application/json";
ret = http_api_post_common (curl, url, token, header, req_content, req_size, rsp_status,
rsp_content, rsp_size, timeout, timeout_sec, pcurl_error);
return ret;
}
static int
http_delete_common (CURL *curl, const char *url,
int *rsp_status, gboolean timeout,
int timeout_sec)
{
int ret = 0;
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
if (seafile_debug_flag_is_set (SEAFILE_DEBUG_CURL)) {
curl_easy_setopt (curl, CURLOPT_VERBOSE, 1);
curl_easy_setopt (curl, CURLOPT_STDERR, seafile_get_logfp());
}
if (timeout) {
/* Set low speed limit to 1 bytes. This effectively means no data. */
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1);
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, timeout_sec);
}
#ifndef USE_GPL_CRYPTO
if (seaf->disable_verify_certificate) {
curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, 0L);
}
#endif
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
#ifndef USE_GPL_CRYPTO
load_ca_bundle (curl);
#endif
gboolean is_https = (strncasecmp(url, "https", strlen("https")) == 0);
set_proxy (curl, is_https);
int rc = curl_easy_perform (curl);
if (rc != 0) {
seaf_warning ("libcurl failed to DELETE %s: %s.\n",
url, curl_easy_strerror(rc));
ret = -1;
goto out;
}
long status;
rc = curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &status);
if (rc != CURLE_OK) {
seaf_warning ("Failed to get status code for DELETE %s.\n", url);
ret = -1;
goto out;
}
*rsp_status = status;
out:
return ret;
}
static int
http_api_delete (CURL *curl, const char *url, const char *token,
int *rsp_status, gboolean timeout, int timeout_sec)
{
char * token_header;
struct curl_slist *headers = NULL;
int ret;
headers = curl_slist_append (headers, "User-Agent: SeaDrive/"SEAFILE_CLIENT_VERSION" ("USER_AGENT_OS")");
if (token) {
token_header = g_strdup_printf ("Authorization: Token %s", token);
headers = curl_slist_append (headers, token_header);
g_free (token_header);
}
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
ret = http_delete_common (curl, url, rsp_status, timeout, timeout_sec);
curl_slist_free_all (headers);
return ret;
}
static int
http_error_to_http_task_error (int status)
{
if (status == HTTP_BAD_REQUEST)
return HTTP_TASK_ERR_BAD_REQUEST;
else if (status == HTTP_FORBIDDEN)
return HTTP_TASK_ERR_FORBIDDEN;
else if (status == HTTP_UNAUTHORIZED)
return HTTP_TASK_ERR_FORBIDDEN;
else if (status >= HTTP_INTERNAL_SERVER_ERROR)
return HTTP_TASK_ERR_SERVER;
else if (status == HTTP_NOT_FOUND)
return HTTP_TASK_ERR_SERVER;
else if (status == HTTP_NO_QUOTA)
return HTTP_TASK_ERR_NO_QUOTA;
else if (status == HTTP_REPO_DELETED)
return HTTP_TASK_ERR_REPO_DELETED;
else if (status == HTTP_REPO_CORRUPTED)
return HTTP_TASK_ERR_REPO_CORRUPTED;
else if (status == HTTP_REQUEST_TIME_OUT || status == HTTP_REPO_TOO_LARGE)
return HTTP_TASK_ERR_LIBRARY_TOO_LARGE;
else
return HTTP_TASK_ERR_UNKNOWN;
}
static void
handle_http_errors (HttpTxTask *task, int status)
{
task->error = http_error_to_http_task_error (status);
}
static int
curl_error_to_http_task_error (int curl_error)
{
if (curl_error == CURLE_SSL_CACERT ||
curl_error == CURLE_PEER_FAILED_VERIFICATION)
return HTTP_TASK_ERR_SSL;
switch (curl_error) {
case CURLE_COULDNT_RESOLVE_PROXY:
return HTTP_TASK_ERR_RESOLVE_PROXY;
case CURLE_COULDNT_RESOLVE_HOST:
return HTTP_TASK_ERR_RESOLVE_HOST;
case CURLE_COULDNT_CONNECT:
return HTTP_TASK_ERR_CONNECT;
case CURLE_OPERATION_TIMEDOUT:
return HTTP_TASK_ERR_TX_TIMEOUT;
case CURLE_SSL_CONNECT_ERROR:
case CURLE_SSL_CERTPROBLEM:
case CURLE_SSL_CACERT_BADFILE:
case CURLE_SSL_ISSUER_ERROR:
return HTTP_TASK_ERR_SSL;
case CURLE_GOT_NOTHING:
case CURLE_SEND_ERROR:
case CURLE_RECV_ERROR:
return HTTP_TASK_ERR_TX;
case CURLE_SEND_FAIL_REWIND:
return HTTP_TASK_ERR_UNHANDLED_REDIRECT;
default:
return HTTP_TASK_ERR_NET;
}
}
static void
handle_curl_errors (HttpTxTask *task, int curl_error)
{
task->error = curl_error_to_http_task_error (curl_error);
}
static void
emit_transfer_done_signal (HttpTxTask *task)
{
if (task->type == HTTP_TASK_TYPE_DOWNLOAD)
g_signal_emit_by_name (seaf, "repo-http-fetched", task);
else
g_signal_emit_by_name (seaf, "repo-http-uploaded", task);
}
static void
transition_state (HttpTxTask *task, int state, int rt_state)
{
seaf_message ("Transfer repo '%.8s': ('%s', '%s') --> ('%s', '%s')\n",
task->repo_id,
http_task_state_to_str(task->state),
http_task_rt_state_to_str(task->runtime_state),
http_task_state_to_str(state),
http_task_rt_state_to_str(rt_state));
if (state != task->state)
task->state = state;
task->runtime_state = rt_state;
if (rt_state == HTTP_TASK_RT_STATE_FINISHED) {
emit_transfer_done_signal (task);
}
}
typedef struct {
char *host;
gboolean use_fileserver_port;
HttpProtocolVersionCallback callback;
void *user_data;
gboolean success;
gboolean not_supported;
int version;
int error_code;
} CheckProtocolData;
static int
parse_protocol_version (const char *rsp_content, int rsp_size, CheckProtocolData *data)
{
json_t *object = NULL;
json_error_t jerror;
int version;
object = json_loadb (rsp_content, rsp_size, 0, &jerror);
if (!object) {
seaf_warning ("Parse response failed: %s.\n", jerror.text);
return -1;
}
if (json_object_has_member (object, "version")) {
version = json_object_get_int_member (object, "version");
data->version = version;
} else {
seaf_warning ("Response doesn't contain protocol version.\n");
json_decref (object);
return -1;
}
json_decref (object);
return 0;
}
static void *
check_protocol_version_thread (void *vdata)
{
CheckProtocolData *data = vdata;
HttpTxPriv *priv = seaf->http_tx_mgr->priv;
ConnectionPool *pool;
Connection *conn;
CURL *curl;
char *url;
int status;
char *rsp_content = NULL;
gint64 rsp_size;
pool = find_connection_pool (priv, data->host);
if (!pool) {
seaf_warning ("Failed to create connection pool for host %s.\n", data->host);
return vdata;
}
conn = connection_pool_get_connection (pool);
if (!conn) {
seaf_warning ("Failed to get connection to host %s.\n", data->host);
return vdata;
}
curl = conn->curl;
if (!data->use_fileserver_port)
url = g_strdup_printf ("%s/seafhttp/protocol-version", data->host);
else
url = g_strdup_printf ("%s/protocol-version", data->host);
int curl_error;
if (http_get (curl, url, NULL, &status, &rsp_content, &rsp_size, NULL, NULL,
TRUE, HTTP_TIMEOUT_SEC, &curl_error) < 0) {
conn->release = TRUE;
data->error_code = curl_error_to_http_task_error (curl_error);
goto out;
}
data->success = TRUE;
if (status == HTTP_OK) {
if (rsp_size == 0)
data->not_supported = TRUE;
else if (parse_protocol_version (rsp_content, rsp_size, data) < 0)
data->not_supported = TRUE;
} else {
seaf_warning ("Bad response code for GET %s: %d.\n", url, status);
data->not_supported = TRUE;
data->error_code = http_error_to_http_task_error (status);
}
out:
g_free (url);
g_free (rsp_content);
connection_pool_return_connection (pool, conn);
return vdata;
}
static void
check_protocol_version_done (void *vdata)
{
CheckProtocolData *data = vdata;
HttpProtocolVersion result;
memset (&result, 0, sizeof(result));
result.check_success = data->success;
result.not_supported = data->not_supported;
result.version = data->version;
result.error_code = data->error_code;
data->callback (&result, data->user_data);
g_free (data->host);
g_free (data);
}
int
http_tx_manager_check_protocol_version (HttpTxManager *manager,
const char *host,
gboolean use_fileserver_port,
HttpProtocolVersionCallback callback,
void *user_data)
{
CheckProtocolData *data = g_new0 (CheckProtocolData, 1);
data->host = g_strdup(host);
data->use_fileserver_port = use_fileserver_port;
data->callback = callback;
data->user_data = user_data;
int ret = seaf_job_manager_schedule_job (seaf->job_mgr,
check_protocol_version_thread,
check_protocol_version_done,
data);
if (ret < 0) {
g_free (data->host);
g_free (data);
}
return ret;
}
typedef struct {
char *host;
gboolean use_notif_server_port;
HttpNotifServerCallback callback;
void *user_data;
gboolean success;
gboolean not_supported;
} CheckNotifServerData;
static void *
check_notif_server_thread (void *vdata)
{
CheckNotifServerData *data = vdata;
HttpTxPriv *priv = seaf->http_tx_mgr->priv;
ConnectionPool *pool;
Connection *conn;
CURL *curl;
char *url;
int status;
char *rsp_content = NULL;
gint64 rsp_size;
pool = find_connection_pool (priv, data->host);
if (!pool) {
seaf_warning ("Failed to create connection pool for host %s.\n", data->host);
return vdata;
}
conn = connection_pool_get_connection (pool);
if (!conn) {
seaf_warning ("Failed to get connection to host %s.\n", data->host);
return vdata;
}
curl = conn->curl;
if (!data->use_notif_server_port)
url = g_strdup_printf ("%s/notification/ping", data->host);
else
url = g_strdup_printf ("%s/ping", data->host);
int curl_error;
if (http_get (curl, url, NULL, &status, &rsp_content, &rsp_size, NULL, NULL, TRUE, HTTP_TIMEOUT_SEC, &curl_error) < 0) {
conn->release = TRUE;
goto out;
}
data->success = TRUE;
if (status != HTTP_OK) {
data->not_supported = TRUE;
}
out:
g_free (url);
g_free (rsp_content);
connection_pool_return_connection (pool, conn);
return vdata;
}
static void
check_notif_server_done (void *vdata)
{
CheckNotifServerData *data = vdata;
data->callback ((data->success && !data->not_supported), data->user_data);
g_free (data->host);
g_free (data);
}
int
http_tx_manager_check_notif_server (HttpTxManager *manager,
const char *host,
gboolean use_notif_server_port,
HttpNotifServerCallback callback,
void *user_data)
{
CheckNotifServerData *data = g_new0 (CheckNotifServerData, 1);
data->host = g_strdup(host);
data->use_notif_server_port = use_notif_server_port;
data->callback = callback;
data->user_data = user_data;
int ret = seaf_job_manager_schedule_job (seaf->job_mgr,
check_notif_server_thread,
check_notif_server_done,
data);
if (ret < 0) {
g_free (data->host);
g_free (data);
}
return ret;
}
/* Check Head Commit. */
typedef struct {
char repo_id[41];
int repo_version;
char *host;
char *token;
gboolean use_fileserver_port;
HttpHeadCommitCallback callback;
void *user_data;
gboolean success;
gboolean is_corrupt;
gboolean is_deleted;
gboolean perm_denied;
char head_commit[41];
int error_code;
} CheckHeadData;
static int
parse_head_commit_info (const char *rsp_content, int rsp_size, CheckHeadData *data)
{
json_t *object = NULL;
json_error_t jerror;
const char *head_commit;
object = json_loadb (rsp_content, rsp_size, 0, &jerror);
if (!object) {
seaf_warning ("Parse response failed: %s.\n", jerror.text);
return -1;
}
if (json_object_has_member (object, "is_corrupted") &&
json_object_get_int_member (object, "is_corrupted"))
data->is_corrupt = TRUE;
if (!data->is_corrupt) {
head_commit = json_object_get_string_member (object, "head_commit_id");
if (!head_commit) {
seaf_warning ("Check head commit for repo %s failed. "
"Response doesn't contain head commit id.\n",
data->repo_id);
json_decref (object);
return -1;
}
memcpy (data->head_commit, head_commit, 40);
}
json_decref (object);
return 0;
}
static void *
check_head_commit_thread (void *vdata)
{
CheckHeadData *data = vdata;
HttpTxPriv *priv = seaf->http_tx_mgr->priv;
ConnectionPool *pool;
Connection *conn;
CURL *curl;
char *url;
int status;
char *rsp_content = NULL;
gint64 rsp_size;
pool = find_connection_pool (priv, data->host);
if (!pool) {
seaf_warning ("Failed to create connection pool for host %s.\n", data->host);
return vdata;
}
conn = connection_pool_get_connection (pool);
if (!conn) {
seaf_warning ("Failed to get connection to host %s.\n", data->host);
return vdata;
}
curl = conn->curl;
if (!data->use_fileserver_port)
url = g_strdup_printf ("%s/seafhttp/repo/%s/commit/HEAD",
data->host, data->repo_id);
else
url = g_strdup_printf ("%s/repo/%s/commit/HEAD",
data->host, data->repo_id);
int curl_error;
if (http_get (curl, url, data->token, &status, &rsp_content, &rsp_size,
NULL, NULL, TRUE, HTTP_TIMEOUT_SEC, &curl_error) < 0) {
conn->release = TRUE;
data->error_code = curl_error_to_http_task_error (curl_error);
goto out;
}
if (status == HTTP_OK) {
if (parse_head_commit_info (rsp_content, rsp_size, data) < 0)
goto out;
data->success = TRUE;
} else if (status == HTTP_FORBIDDEN) {
data->perm_denied = TRUE;
} else if (status == HTTP_REPO_DELETED) {
data->is_deleted = TRUE;
data->success = TRUE;
} else {
seaf_warning ("Bad response code for GET %s: %d.\n", url, status);
data->error_code = http_error_to_http_task_error (status);
}
out:
g_free (url);
g_free (rsp_content);
connection_pool_return_connection (pool, conn);
return vdata;
}
static void
check_head_commit_done (void *vdata)
{
CheckHeadData *data = vdata;
HttpHeadCommit result;
memset (&result, 0, sizeof(result));
result.check_success = data->success;
result.is_corrupt = data->is_corrupt;
result.is_deleted = data->is_deleted;
result.perm_denied = data->perm_denied;
memcpy (result.head_commit, data->head_commit, 40);
result.error_code = data->error_code;
data->callback (&result, data->user_data);
g_free (data->host);
g_free (data->token);
g_free (data);
}
int
http_tx_manager_check_head_commit (HttpTxManager *manager,
const char *repo_id,
int repo_version,
const char *host,
const char *token,
gboolean use_fileserver_port,
HttpHeadCommitCallback callback,
void *user_data)
{
CheckHeadData *data = g_new0 (CheckHeadData, 1);
memcpy (data->repo_id, repo_id, 36);
data->repo_version = repo_version;
data->host = g_strdup(host);
data->token = g_strdup(token);
data->callback = callback;
data->user_data = user_data;
data->use_fileserver_port = use_fileserver_port;
if (seaf_job_manager_schedule_job (seaf->job_mgr,
check_head_commit_thread,
check_head_commit_done,
data) < 0) {
g_free (data->host);
g_free (data->token);
g_free (data);
return -1;
}
return 0;
}
/* Get folder permissions. */
void
http_folder_perm_req_free (HttpFolderPermReq *req)
{
if (!req)
return;
g_free (req->token);
g_free (req);
}
void
http_folder_perm_res_free (HttpFolderPermRes *res)
{
GList *ptr;
if (!res)
return;
for (ptr = res->user_perms; ptr; ptr = ptr->next)
folder_perm_free ((FolderPerm *)ptr->data);
for (ptr = res->group_perms; ptr; ptr = ptr->next)
folder_perm_free ((FolderPerm *)ptr->data);
g_free (res);
}
typedef struct {
char *host;
gboolean use_fileserver_port;
GList *requests;
HttpGetFolderPermsCallback callback;
void *user_data;
gboolean success;
GList *results;
} GetFolderPermsData;
/* Make sure the path starts with '/' but doesn't end with '/'. */
static char *
canonical_perm_path (const char *path)
{
int len = strlen(path);
char *copy, *ret;
if (strcmp (path, "/") == 0)
return g_strdup(path);
if (path[0] == '/' && path[len-1] != '/')
return g_strdup(path);
copy = g_strdup(path);
if (copy[len-1] == '/')
copy[len-1] = 0;
if (copy[0] != '/')
ret = g_strconcat ("/", copy, NULL);
else
ret = copy;
return ret;
}
static GList *
parse_permission_list (json_t *array, gboolean *error)
{
GList *ret = NULL, *ptr;
json_t *object, *member;
size_t n;
int i;
FolderPerm *perm;
const char *str;
*error = FALSE;
n = json_array_size (array);
for (i = 0; i < n; ++i) {
object = json_array_get (array, i);
perm = g_new0 (FolderPerm, 1);
member = json_object_get (object, "path");
if (!member) {
seaf_warning ("Invalid folder perm response format: no path.\n");
*error = TRUE;
goto out;
}
str = json_string_value(member);
if (!str) {
seaf_warning ("Invalid folder perm response format: invalid path.\n");
*error = TRUE;
goto out;
}
perm->path = canonical_perm_path (str);
member = json_object_get (object, "permission");
if (!member) {
seaf_warning ("Invalid folder perm response format: no permission.\n");
*error = TRUE;
goto out;
}
str = json_string_value(member);
if (!str) {
seaf_warning ("Invalid folder perm response format: invalid permission.\n");
*error = TRUE;
goto out;
}
perm->permission = g_strdup(str);
ret = g_list_append (ret, perm);
}
out:
if (*error) {
for (ptr = ret; ptr; ptr = ptr->next)
folder_perm_free ((FolderPerm *)ptr->data);
g_list_free (ret);
ret = NULL;
}
return ret;
}
static int
parse_folder_perms (const char *rsp_content, int rsp_size, GetFolderPermsData *data)
{
json_t *array = NULL, *object, *member;
json_error_t jerror;
size_t n;
int i;
GList *results = NULL, *ptr;
HttpFolderPermRes *res;
const char *repo_id;
int ret = 0;
gboolean error;
array = json_loadb (rsp_content, rsp_size, 0, &jerror);
if (!array) {
seaf_warning ("Parse response failed: %s.\n", jerror.text);
return -1;
}
n = json_array_size (array);
for (i = 0; i < n; ++i) {
object = json_array_get (array, i);
res = g_new0 (HttpFolderPermRes, 1);
member = json_object_get (object, "repo_id");
if (!member) {
seaf_warning ("Invalid folder perm response format: no repo_id.\n");
ret = -1;
goto out;
}
repo_id = json_string_value(member);
if (strlen(repo_id) != 36) {
seaf_warning ("Invalid folder perm response format: invalid repo_id.\n");
ret = -1;
goto out;
}
memcpy (res->repo_id, repo_id, 36);
member = json_object_get (object, "ts");
if (!member) {
seaf_warning ("Invalid folder perm response format: no timestamp.\n");
ret = -1;
goto out;
}
res->timestamp = json_integer_value (member);
member = json_object_get (object, "user_perms");
if (!member) {
seaf_warning ("Invalid folder perm response format: no user_perms.\n");
ret = -1;
goto out;
}
res->user_perms = parse_permission_list (member, &error);
if (error) {
ret = -1;
goto out;
}
member = json_object_get (object, "group_perms");
if (!member) {
seaf_warning ("Invalid folder perm response format: no group_perms.\n");
ret = -1;
goto out;
}
res->group_perms = parse_permission_list (member, &error);
if (error) {
ret = -1;
goto out;
}
results = g_list_append (results, res);
}
out:
json_decref (array);
if (ret < 0) {
for (ptr = results; ptr; ptr = ptr->next)
http_folder_perm_res_free ((HttpFolderPermRes *)ptr->data);
g_list_free (results);
} else {
data->results = results;
}
return ret;
}
static char *
compose_get_folder_perms_request (GList *requests)
{
GList *ptr;
HttpFolderPermReq *req;
json_t *object, *array;
char *req_str = NULL;
array = json_array ();
for (ptr = requests; ptr; ptr = ptr->next) {
req = ptr->data;
object = json_object ();
json_object_set_new (object, "repo_id", json_string(req->repo_id));
json_object_set_new (object, "token", json_string(req->token));
json_object_set_new (object, "ts", json_integer(req->timestamp));
json_array_append_new (array, object);
}
req_str = json_dumps (array, 0);
if (!req_str) {
seaf_warning ("Faile to json_dumps.\n");
}
json_decref (array);
return req_str;
}
static void *
get_folder_perms_thread (void *vdata)
{
GetFolderPermsData *data = vdata;
HttpTxPriv *priv = seaf->http_tx_mgr->priv;
ConnectionPool *pool;
Connection *conn;
CURL *curl;
char *url;
char *req_content = NULL;
int status;
char *rsp_content = NULL;
gint64 rsp_size;
GList *ptr;
pool = find_connection_pool (priv, data->host);
if (!pool) {
seaf_warning ("Failed to create connection pool for host %s.\n", data->host);
return vdata;
}
conn = connection_pool_get_connection (pool);
if (!conn) {
seaf_warning ("Failed to get connection to host %s.\n", data->host);
return vdata;
}
curl = conn->curl;
if (!data->use_fileserver_port)
url = g_strdup_printf ("%s/seafhttp/repo/folder-perm", data->host);
else
url = g_strdup_printf ("%s/repo/folder-perm", data->host);
req_content = compose_get_folder_perms_request (data->requests);
if (!req_content)
goto out;
if (http_post (curl, url, NULL, req_content, strlen(req_content),
&status, &rsp_content, &rsp_size, TRUE, HTTP_TIMEOUT_SEC, NULL) < 0) {
conn->release = TRUE;
goto out;
}
if (status == HTTP_OK) {
if (parse_folder_perms (rsp_content, rsp_size, data) < 0)
goto out;
data->success = TRUE;
} else {
seaf_warning ("Bad response code for GET %s: %d.\n", url, status);
}
out:
for (ptr = data->requests; ptr; ptr = ptr->next)
http_folder_perm_req_free ((HttpFolderPermReq *)ptr->data);
g_list_free (data->requests);
g_free (url);
g_free (req_content);
g_free (rsp_content);
connection_pool_return_connection (pool, conn);
return vdata;
}
static void
get_folder_perms_done (void *vdata)
{
GetFolderPermsData *data = vdata;
HttpFolderPerms cb_data;
memset (&cb_data, 0, sizeof(cb_data));
cb_data.success = data->success;
cb_data.results = data->results;
data->callback (&cb_data, data->user_data);
GList *ptr;
for (ptr = data->results; ptr; ptr = ptr->next)
http_folder_perm_res_free ((HttpFolderPermRes *)ptr->data);
g_list_free (data->results);
g_free (data->host);
g_free (data);
}
int
http_tx_manager_get_folder_perms (HttpTxManager *manager,
const char *host,
gboolean use_fileserver_port,
GList *folder_perm_requests,
HttpGetFolderPermsCallback callback,
void *user_data)
{
GetFolderPermsData *data = g_new0 (GetFolderPermsData, 1);
data->host = g_strdup(host);
data->requests = folder_perm_requests;
data->callback = callback;
data->user_data = user_data;
data->use_fileserver_port = use_fileserver_port;
if (seaf_job_manager_schedule_job (seaf->job_mgr,
get_folder_perms_thread,
get_folder_perms_done,
data) < 0) {
g_free (data->host);
g_free (data);
return -1;
}
return 0;
}
/* Get Locked Files. */
void
http_locked_files_req_free (HttpLockedFilesReq *req)
{
if (!req)
return;
g_free (req->token);
g_free (req);
}
void
http_locked_files_res_free (HttpLockedFilesRes *res)
{
if (!res)
return;
g_hash_table_destroy (res->locked_files);
g_free (res);
}
typedef struct {
char *host;
gboolean use_fileserver_port;
GList *requests;
HttpGetLockedFilesCallback callback;
void *user_data;
gboolean success;
GList *results;
} GetLockedFilesData;
static GHashTable *
parse_locked_file_list (json_t *array)
{
GHashTable *ret = NULL;
size_t n, i;
json_t *obj, *string, *integer;
ret = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
if (!ret) {
return NULL;
}
n = json_array_size (array);
for (i = 0; i < n; ++i) {
obj = json_array_get (array, i);
string = json_object_get (obj, "path");
if (!string) {
g_hash_table_destroy (ret);
return NULL;
}
integer = json_object_get (obj, "by_me");
if (!integer) {
g_hash_table_destroy (ret);
return NULL;
}
g_hash_table_insert (ret,
g_strdup(json_string_value(string)),
(void*)(long)json_integer_value(integer));
}
return ret;
}
static int
parse_locked_files (const char *rsp_content, int rsp_size, GetLockedFilesData *data)
{
json_t *array = NULL, *object, *member;
json_error_t jerror;
size_t n;
int i;
GList *results = NULL;
HttpLockedFilesRes *res;
const char *repo_id;
int ret = 0;
array = json_loadb (rsp_content, rsp_size, 0, &jerror);
if (!array) {
seaf_warning ("Parse response failed: %s.\n", jerror.text);
return -1;
}
n = json_array_size (array);
for (i = 0; i < n; ++i) {
object = json_array_get (array, i);
res = g_new0 (HttpLockedFilesRes, 1);
member = json_object_get (object, "repo_id");
if (!member) {
seaf_warning ("Invalid locked files response format: no repo_id.\n");
ret = -1;
goto out;
}
repo_id = json_string_value(member);
if (strlen(repo_id) != 36) {
seaf_warning ("Invalid locked files response format: invalid repo_id.\n");
ret = -1;
goto out;
}
memcpy (res->repo_id, repo_id, 36);
member = json_object_get (object, "ts");
if (!member) {
seaf_warning ("Invalid locked files response format: no timestamp.\n");
ret = -1;
goto out;
}
res->timestamp = json_integer_value (member);
member = json_object_get (object, "locked_files");
if (!member) {
seaf_warning ("Invalid locked files response format: no locked_files.\n");
ret = -1;
goto out;
}
res->locked_files = parse_locked_file_list (member);
if (res->locked_files == NULL) {
ret = -1;
goto out;
}
results = g_list_append (results, res);
}
out:
json_decref (array);
if (ret < 0) {
g_list_free_full (results, (GDestroyNotify)http_locked_files_res_free);
} else {
data->results = results;
}
return ret;
}
static char *
compose_get_locked_files_request (GList *requests)
{
GList *ptr;
HttpLockedFilesReq *req;
json_t *object, *array;
char *req_str = NULL;
array = json_array ();
for (ptr = requests; ptr; ptr = ptr->next) {
req = ptr->data;
object = json_object ();
json_object_set_new (object, "repo_id", json_string(req->repo_id));
json_object_set_new (object, "token", json_string(req->token));
json_object_set_new (object, "ts", json_integer(req->timestamp));
json_array_append_new (array, object);
}
req_str = json_dumps (array, 0);
if (!req_str) {
seaf_warning ("Faile to json_dumps.\n");
}
json_decref (array);
return req_str;
}
static void *
get_locked_files_thread (void *vdata)
{
GetLockedFilesData *data = vdata;
HttpTxPriv *priv = seaf->http_tx_mgr->priv;
ConnectionPool *pool;
Connection *conn;
CURL *curl;
char *url;
char *req_content = NULL;
int status;
char *rsp_content = NULL;
gint64 rsp_size;
pool = find_connection_pool (priv, data->host);
if (!pool) {
seaf_warning ("Failed to create connection pool for host %s.\n", data->host);
return vdata;
}
conn = connection_pool_get_connection (pool);
if (!conn) {
seaf_warning ("Failed to get connection to host %s.\n", data->host);
return vdata;
}
curl = conn->curl;
if (!data->use_fileserver_port)
url = g_strdup_printf ("%s/seafhttp/repo/locked-files", data->host);
else
url = g_strdup_printf ("%s/repo/locked-files", data->host);
req_content = compose_get_locked_files_request (data->requests);
if (!req_content)
goto out;
if (http_post (curl, url, NULL, req_content, strlen(req_content),
&status, &rsp_content, &rsp_size, TRUE, HTTP_TIMEOUT_SEC, NULL) < 0) {
conn->release = TRUE;
goto out;
}
if (status == HTTP_OK) {
if (parse_locked_files (rsp_content, rsp_size, data) < 0)
goto out;
data->success = TRUE;
} else {
seaf_warning ("Bad response code for GET %s: %d.\n", url, status);
}
out:
g_list_free_full (data->requests, (GDestroyNotify)http_locked_files_req_free);
g_free (url);
g_free (req_content);
g_free (rsp_content);
connection_pool_return_connection (pool, conn);
return vdata;
}
static void
get_locked_files_done (void *vdata)
{
GetLockedFilesData *data = vdata;
HttpLockedFiles cb_data;
memset (&cb_data, 0, sizeof(cb_data));
cb_data.success = data->success;
cb_data.results = data->results;
data->callback (&cb_data, data->user_data);
g_list_free_full (data->results, (GDestroyNotify)http_locked_files_res_free);
g_free (data->host);
g_free (data);
}
int
http_tx_manager_get_locked_files (HttpTxManager *manager,
const char *host,
gboolean use_fileserver_port,
GList *locked_files_requests,
HttpGetLockedFilesCallback callback,
void *user_data)
{
GetLockedFilesData *data = g_new0 (GetLockedFilesData, 1);
data->host = g_strdup(host);
data->requests = locked_files_requests;
data->callback = callback;
data->user_data = user_data;
data->use_fileserver_port = use_fileserver_port;
if (seaf_job_manager_schedule_job (seaf->job_mgr,
get_locked_files_thread,
get_locked_files_done,
data) < 0) {
g_free (data->host);
g_free (data);
return -1;
}
return 0;
}
/* Synchronous interfaces for locking/unlocking a file on the server. */
int
http_tx_manager_lock_file (HttpTxManager *manager,
const char *host,
gboolean use_fileserver_port,
const char *token,
const char *repo_id,
const char *path)
{
HttpTxPriv *priv = seaf->http_tx_mgr->priv;
ConnectionPool *pool;
Connection *conn;
CURL *curl;
char *url;
int status;
int ret = 0;
pool = find_connection_pool (priv, host);
if (!pool) {
seaf_warning ("Failed to create connection pool for host %s.\n", host);
return -1;
}
conn = connection_pool_get_connection (pool);
if (!conn) {
seaf_warning ("Failed to get connection to host %s.\n", host);
return -1;
}
curl = conn->curl;
char *esc_path = g_uri_escape_string(path, NULL, FALSE);
if (!use_fileserver_port)
url = g_strdup_printf ("%s/seafhttp/repo/%s/lock-file?p=%s", host, repo_id, esc_path);
else
url = g_strdup_printf ("%s/repo/%s/lock-file?p=%s", host, repo_id, esc_path);
g_free (esc_path);
if (http_put (curl, url, token, NULL, 0, NULL, NULL,
&status, NULL, NULL, TRUE, NULL) < 0) {
conn->release = TRUE;
ret = -1;
goto out;
}
if (status != HTTP_OK) {
seaf_warning ("Bad response code for PUT %s: %d.\n", url, status);
ret = -1;
}
out:
g_free (url);
connection_pool_return_connection (pool, conn);
return ret;
}
int
http_tx_manager_unlock_file (HttpTxManager *manager,
const char *host,
gboolean use_fileserver_port,
const char *token,
const char *repo_id,
const char *path)
{
HttpTxPriv *priv = seaf->http_tx_mgr->priv;
ConnectionPool *pool;
Connection *conn;
CURL *curl;
char *url;
int status;
int ret = 0;
pool = find_connection_pool (priv, host);
if (!pool) {
seaf_warning ("Failed to create connection pool for host %s.\n", host);
return -1;
}
conn = connection_pool_get_connection (pool);
if (!conn) {
seaf_warning ("Failed to get connection to host %s.\n", host);
return -1;
}
curl = conn->curl;
char *esc_path = g_uri_escape_string(path, NULL, FALSE);
if (!use_fileserver_port)
url = g_strdup_printf ("%s/seafhttp/repo/%s/unlock-file?p=%s", host, repo_id, esc_path);
else
url = g_strdup_printf ("%s/repo/%s/unlock-file?p=%s", host, repo_id, esc_path);
g_free (esc_path);
if (http_put (curl, url, token, NULL, 0, NULL, NULL,
&status, NULL, NULL, TRUE, NULL) < 0) {
conn->release = TRUE;
ret = -1;
goto out;
}
if (status != HTTP_OK) {
seaf_warning ("Bad response code for PUT %s: %d.\n", url, status);
ret = -1;
}
out:
g_free (url);
connection_pool_return_connection (pool, conn);
return ret;
}
/* Asynchronous interface for sending a API GET request to seahub. */
typedef struct {
char *host;
char *url;
char *api_token;
HttpAPIGetCallback callback;
void *user_data;
gboolean success;
char *rsp_content;
int rsp_size;
int error_code;
int http_status;
} APIGetData;
static void *
api_get_request_thread (void *vdata)
{
APIGetData *data = vdata;
HttpTxPriv *priv = seaf->http_tx_mgr->priv;
ConnectionPool *pool;
Connection *conn;
CURL *curl;
int status;
char *rsp_content = NULL;
gint64 rsp_size;
pool = find_connection_pool (priv, data->host);
if (!pool) {
seaf_warning ("Failed to create connection pool for host %s.\n", data->host);
return vdata;
}
conn = connection_pool_get_connection (pool);
if (!conn) {
seaf_warning ("Failed to get connection to host %s.\n", data->host);
return vdata;
}
curl = conn->curl;
int curl_error;
if (http_api_get (curl, data->url, data->api_token,
&status, &rsp_content, &rsp_size,
NULL, NULL, TRUE, HTTP_TIMEOUT_SEC, &curl_error) < 0) {
conn->release = TRUE;
data->error_code = curl_error_to_http_task_error (curl_error);
goto out;
}
// Free rsp_content even if status is not HTTP_OK, prevent memory leak
data->rsp_content = rsp_content;
data->rsp_size = rsp_size;
if (status == HTTP_OK) {
data->success = TRUE;
} else {
seaf_warning ("Bad response code for GET %s: %d.\n", data->url, status);
data->error_code = http_error_to_http_task_error (status);
}
out:
connection_pool_return_connection (pool, conn);
return vdata;
}
static void
api_get_request_done (void *vdata)
{
APIGetData *data = vdata;
HttpAPIGetResult cb_data;
memset (&cb_data, 0, sizeof(cb_data));
cb_data.success = data->success;
cb_data.rsp_content = data->rsp_content;
cb_data.rsp_size = data->rsp_size;
cb_data.error_code = data->error_code;
data->callback (&cb_data, data->user_data);
g_free (data->rsp_content);
g_free (data->host);
g_free (data->url);
g_free (data->api_token);
g_free (data);
}
int
http_tx_manager_api_get (HttpTxManager *manager,
const char *host,
const char *url,
const char *api_token,
HttpAPIGetCallback callback,
void *user_data)
{
APIGetData *data = g_new0 (APIGetData, 1);
data->host = g_strdup(host);
data->url = g_strdup(url);
data->api_token = g_strdup(api_token);
data->callback = callback;
data->user_data = user_data;
if (seaf_job_manager_schedule_job (seaf->job_mgr,
api_get_request_thread,
api_get_request_done,
data) < 0) {
g_free (data->host);
g_free (data->url);
g_free (data->api_token);
g_free (data);
return -1;
}
return 0;
}
static void *
fileserver_api_get_request (void *vdata)
{
APIGetData *data = vdata;
HttpTxPriv *priv = seaf->http_tx_mgr->priv;
ConnectionPool *pool;
Connection *conn;
CURL *curl;
int status;
char *rsp_content = NULL;
gint64 rsp_size;
pool = find_connection_pool (priv, data->host);
if (!pool) {
seaf_warning ("Failed to create connection pool for host %s.\n", data->host);
return vdata;
}
conn = connection_pool_get_connection (pool);
if (!conn) {
seaf_warning ("Failed to get connection to host %s.\n", data->host);
return vdata;
}
curl = conn->curl;
int curl_error;
if (http_get (curl, data->url, data->api_token, &status, &rsp_content, &rsp_size, NULL, NULL,
TRUE, HTTP_TIMEOUT_SEC, &curl_error) < 0) {
conn->release = TRUE;
data->error_code = curl_error_to_http_task_error (curl_error);
goto out;
}
data->rsp_content = rsp_content;
data->rsp_size = rsp_size;
if (status == HTTP_OK) {
data->success = TRUE;
} else {
seaf_warning ("Bad response code for GET %s: %d.\n", data->url, status);
data->error_code = http_error_to_http_task_error (status);
data->http_status = status;
}
out:
connection_pool_return_connection (pool, conn);
return vdata;
}
static void
fileserver_api_get_request_done (void *vdata)
{
APIGetData *data = vdata;
HttpAPIGetResult cb_data;
memset (&cb_data, 0, sizeof(cb_data));
cb_data.success = data->success;
cb_data.rsp_content = data->rsp_content;
cb_data.rsp_size = data->rsp_size;
cb_data.error_code = data->error_code;
cb_data.http_status = data->http_status;
data->callback (&cb_data, data->user_data);
g_free (data->rsp_content);
g_free (data->host);
g_free (data->url);
g_free (data->api_token);
g_free (data);
}
int
http_tx_manager_fileserver_api_get (HttpTxManager *manager,
const char *host,
const char *url,
const char *api_token,
HttpAPIGetCallback callback,
void *user_data)
{
APIGetData *data = g_new0 (APIGetData, 1);
data->host = g_strdup(host);
data->url = g_strdup(url);
data->api_token = g_strdup(api_token);
data->callback = callback;
data->user_data = user_data;
if (seaf_job_manager_schedule_job (seaf->job_mgr,
fileserver_api_get_request,
fileserver_api_get_request_done,
data) < 0) {
g_free (data->rsp_content);
g_free (data->host);
g_free (data->url);
g_free (data->api_token);
g_free (data);
return -1;
}
return 0;
}
static gboolean
remove_task_help (gpointer key, gpointer value, gpointer user_data)
{
HttpTxTask *task = value;
const char *repo_id = user_data;
if (strcmp(task->repo_id, repo_id) != 0)
return FALSE;
return TRUE;
}
static void
clean_tasks_for_repo (HttpTxManager *manager, const char *repo_id)
{
g_hash_table_foreach_remove (manager->priv->download_tasks,
remove_task_help, (gpointer)repo_id);
g_hash_table_foreach_remove (manager->priv->upload_tasks,
remove_task_help, (gpointer)repo_id);
}
static int
check_permission (HttpTxTask *task, Connection *conn)
{
CURL *curl;
char *url;
int status;
int ret = 0;
char *rsp_content = NULL;
gint64 rsp_size;
json_t *rsp_obj = NULL, *reason = NULL, *unsyncable_path = NULL;
const char *reason_str = NULL, *unsyncable_path_str = NULL;
json_error_t jerror;
curl = conn->curl;
const char *type = (task->type == HTTP_TASK_TYPE_DOWNLOAD) ? "download" : "upload";
const char *url_prefix = (task->use_fileserver_port) ? "" : "seafhttp/";
char *client_name = g_uri_escape_string (seaf->client_name, NULL, FALSE);
url = g_strdup_printf ("%s/%srepo/%s/permission-check/?op=%s"
"&client_id=%s&client_name=%s",
task->host, url_prefix, task->repo_id, type,
seaf->client_id, client_name);
g_free (client_name);
int curl_error;
if (http_get (curl, url, task->token, &status, &rsp_content, &rsp_size, NULL, NULL,
TRUE, HTTP_TIMEOUT_SEC, &curl_error) < 0) {
conn->release = TRUE;
handle_curl_errors (task, curl_error);
ret = -1;
goto out;
}
if (status != HTTP_OK) {
seaf_warning ("Bad response code for GET %s: %d.\n", url, status);
if (status != HTTP_FORBIDDEN || !rsp_content) {
handle_http_errors (task, status);
ret = -1;
goto out;
}
rsp_obj = json_loadb (rsp_content, rsp_size, 0 ,&jerror);
if (!rsp_obj) {
seaf_warning ("Parse check permission response failed: %s.\n", jerror.text);
handle_http_errors (task, status);
json_decref (rsp_obj);
ret = -1;
goto out;
}
reason = json_object_get (rsp_obj, "reason");
if (!reason) {
handle_http_errors (task, status);
json_decref (rsp_obj);
ret = -1;
goto out;
}
reason_str = json_string_value (reason);
if (g_strcmp0 (reason_str, "no write permission") == 0) {
task->error = HTTP_TASK_ERR_NO_WRITE_PERMISSION;
} else if (g_strcmp0 (reason_str, "unsyncable share permission") == 0) {
task->error = HTTP_TASK_ERR_NO_PERMISSION_TO_SYNC;
unsyncable_path = json_object_get (rsp_obj, "unsyncable_path");
if (!unsyncable_path) {
json_decref (rsp_obj);
ret = -1;
goto out;
}
unsyncable_path_str = json_string_value (unsyncable_path);
if (unsyncable_path_str)
task->unsyncable_path = g_strdup (unsyncable_path_str);
} else {
task->error = HTTP_TASK_ERR_FORBIDDEN;
}
ret = -1;
}
out:
g_free (url);
curl_easy_reset (curl);
return ret;
}
/* Upload. */
static void *http_upload_thread (void *vdata);
static void http_upload_done (void *vdata);
int
http_tx_manager_add_upload (HttpTxManager *manager,
const char *repo_id,
int repo_version,
const char *server,
const char *user,
const char *repo_uname,
const char *host,
const char *token,
int protocol_version,
gboolean use_fileserver_port,
GError **error)
{
HttpTxTask *task;
if (!repo_id || !repo_uname || !token || !host) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Empty argument(s)");
return -1;
}
clean_tasks_for_repo (manager, repo_id);
task = http_tx_task_new (manager, repo_id, repo_version,
HTTP_TASK_TYPE_UPLOAD, FALSE,
host, token);
task->repo_uname = g_strdup (repo_uname);
task->protocol_version = protocol_version;
task->state = HTTP_TASK_STATE_NORMAL;
task->use_fileserver_port = use_fileserver_port;
task->server = g_strdup (server);
task->user = g_strdup (user);
g_hash_table_insert (manager->priv->upload_tasks,
g_strdup(repo_id),
task);
if (seaf_job_manager_schedule_job (seaf->job_mgr,
http_upload_thread,
http_upload_done,
task) < 0) {
g_hash_table_remove (manager->priv->upload_tasks, repo_id);
return -1;
}
return 0;
}
static gboolean
dirent_same (SeafDirent *dent1, SeafDirent *dent2)
{
return (strcmp(dent1->id, dent2->id) == 0 &&
dent1->mode == dent2->mode &&
dent1->mtime == dent2->mtime);
}
typedef struct {
HttpTxTask *task;
gint64 delta;
GHashTable *active_paths;
} CalcQuotaDeltaData;
static int
check_quota_and_active_paths_diff_files (int n, const char *basedir,
SeafDirent *files[], void *vdata)
{
CalcQuotaDeltaData *data = vdata;
SeafDirent *file1 = files[0];
SeafDirent *file2 = files[1];
gint64 size1, size2;
char *path;
if (file1 && file2) {
size1 = file1->size;
size2 = file2->size;
data->delta += (size1 - size2);
if (!dirent_same (file1, file2)) {
path = g_strconcat(basedir, file1->name, NULL);
g_hash_table_replace (data->active_paths, path, (void*)(long)S_IFREG);
}
} else if (file1 && !file2) {
data->delta += file1->size;
path = g_strconcat (basedir, file1->name, NULL);
g_hash_table_replace (data->active_paths, path, (void*)(long)S_IFREG);
} else if (!file1 && file2) {
data->delta -= file2->size;
}
return 0;
}
static int
check_quota_and_active_paths_diff_dirs (int n, const char *basedir,
SeafDirent *dirs[], void *vdata,
gboolean *recurse)
{
CalcQuotaDeltaData *data = vdata;
SeafDirent *dir1 = dirs[0];
SeafDirent *dir2 = dirs[1];
char *path;
/* When a new empty dir is created, or a dir became empty. */
if ((!dir2 && dir1 && strcmp(dir1->id, EMPTY_SHA1) == 0) ||
(dir2 && dir1 && strcmp(dir1->id, EMPTY_SHA1) == 0 && strcmp(dir2->id, EMPTY_SHA1) != 0)) {
path = g_strconcat (basedir, dir1->name, NULL);
g_hash_table_replace (data->active_paths, path, (void*)(long)S_IFDIR);
}
return 0;
}
static int
calculate_upload_size_delta_and_active_paths (HttpTxTask *task,
gint64 *delta,
GHashTable *active_paths)
{
int ret = 0;
SeafBranch *local = NULL, *master = NULL;
SeafCommit *local_head = NULL, *master_head = NULL;
local = seaf_branch_manager_get_branch (seaf->branch_mgr, task->repo_id, "local");
if (!local) {
seaf_warning ("Branch local not found for repo %.8s.\n", task->repo_id);
ret = -1;
goto out;
}
master = seaf_branch_manager_get_branch (seaf->branch_mgr, task->repo_id, "master");
if (!master) {
seaf_warning ("Branch master not found for repo %.8s.\n", task->repo_id);
ret = -1;
goto out;
}
local_head = seaf_commit_manager_get_commit (seaf->commit_mgr,
task->repo_id, task->repo_version,
local->commit_id);
if (!local_head) {
seaf_warning ("Local head commit not found for repo %.8s.\n",
task->repo_id);
ret = -1;
goto out;
}
master_head = seaf_commit_manager_get_commit (seaf->commit_mgr,
task->repo_id, task->repo_version,
master->commit_id);
if (!master_head) {
seaf_warning ("Master head commit not found for repo %.8s.\n",
task->repo_id);
ret = -1;
goto out;
}
CalcQuotaDeltaData data;
memset (&data, 0, sizeof(data));
data.task = task;
data.active_paths = active_paths;
DiffOptions opts;
memset (&opts, 0, sizeof(opts));
memcpy (opts.store_id, task->repo_id, 36);
opts.version = task->repo_version;
opts.file_cb = check_quota_and_active_paths_diff_files;
opts.dir_cb = check_quota_and_active_paths_diff_dirs;
opts.data = &data;
const char *trees[2];
trees[0] = local_head->root_id;
trees[1] = master_head->root_id;
if (diff_trees (2, trees, &opts) < 0) {
seaf_warning ("Failed to diff local and master head for repo %.8s.\n",
task->repo_id);
ret = -1;
goto out;
}
*delta = data.delta;
out:
seaf_branch_unref (local);
seaf_branch_unref (master);
seaf_commit_unref (local_head);
seaf_commit_unref (master_head);
return ret;
}
static int
check_quota (HttpTxTask *task, Connection *conn, gint64 delta)
{
CURL *curl;
char *url;
int status;
int ret = 0;
curl = conn->curl;
if (!task->use_fileserver_port)
url = g_strdup_printf ("%s/seafhttp/repo/%s/quota-check/?delta=%"G_GINT64_FORMAT"",
task->host, task->repo_id, delta);
else
url = g_strdup_printf ("%s/repo/%s/quota-check/?delta=%"G_GINT64_FORMAT"",
task->host, task->repo_id, delta);
int curl_error;
if (http_get (curl, url, task->token, &status, NULL, NULL, NULL, NULL,
TRUE, HTTP_TIMEOUT_SEC, &curl_error) < 0) {
conn->release = TRUE;
handle_curl_errors (task, curl_error);
ret = -1;
goto out;
}
if (status != HTTP_OK) {
seaf_warning ("Bad response code for GET %s: %d.\n", url, status);
handle_http_errors (task, status);
ret = -1;
}
out:
g_free (url);
curl_easy_reset (curl);
return ret;
}
static int
send_commit_object (HttpTxTask *task, Connection *conn)
{
CURL *curl;
char *url;
int status;
char *data;
int len;
int ret = 0;
if (seaf_obj_store_read_obj (seaf->commit_mgr->obj_store,
task->repo_id, task->repo_version,
task->head, (void**)&data, &len) < 0) {
seaf_warning ("Failed to read commit %s.\n", task->head);
task->error = HTTP_TASK_ERR_BAD_LOCAL_DATA;
return -1;
}
curl = conn->curl;
if (!task->use_fileserver_port)
url = g_strdup_printf ("%s/seafhttp/repo/%s/commit/%s",
task->host, task->repo_id, task->head);
else
url = g_strdup_printf ("%s/repo/%s/commit/%s",
task->host, task->repo_id, task->head);
int curl_error;
if (http_put (curl, url, task->token,
data, len,
NULL, NULL,
&status, NULL, NULL, TRUE, &curl_error) < 0) {
conn->release = TRUE;
handle_curl_errors (task, curl_error);
ret = -1;
goto out;
}
if (status != HTTP_OK) {
seaf_warning ("Bad response code for PUT %s: %d.\n", url, status);
handle_http_errors (task, status);
ret = -1;
}
out:
g_free (url);
curl_easy_reset (curl);
g_free (data);
return ret;
}
typedef struct {
GList **pret;
GHashTable *checked_objs;
GHashTable *needed_file_pair;
GHashTable *deleted_file_pair;
} CalcFsListData;
static int
collect_file_ids (int n, const char *basedir, SeafDirent *files[], void *vdata)
{
SeafDirent *file1 = files[0];
SeafDirent *file2 = files[1];
CalcFsListData *data = vdata;
GList **pret = data->pret;
int dummy;
if (!file1 && file2) {
g_hash_table_replace (data->deleted_file_pair, g_strdup(file2->id),
g_strconcat(basedir, file2->name, NULL));
return 0;
}
if (strcmp (file1->id, EMPTY_SHA1) == 0)
return 0;
if (g_hash_table_lookup (data->checked_objs, file1->id))
return 0;
if (!file2 || strcmp (file1->id, file2->id) != 0) {
*pret = g_list_prepend (*pret, g_strdup(file1->id));
g_hash_table_insert (data->checked_objs, g_strdup(file1->id), &dummy);
g_hash_table_replace (data->needed_file_pair, g_strdup(file1->id),
g_strconcat (basedir, file1->name, NULL));
}
return 0;
}
static int
collect_dir_ids (int n, const char *basedir, SeafDirent *dirs[], void *vdata,
gboolean *recurse)
{
SeafDirent *dir1 = dirs[0];
SeafDirent *dir2 = dirs[1];
CalcFsListData *data = vdata;
GList **pret = data->pret;
int dummy;
if (!dir1 || strcmp (dir1->id, EMPTY_SHA1) == 0)
return 0;
if (g_hash_table_lookup (data->checked_objs, dir1->id))
return 0;
if (!dir2 || strcmp (dir1->id, dir2->id) != 0) {
*pret = g_list_prepend (*pret, g_strdup(dir1->id));
g_hash_table_insert (data->checked_objs, g_strdup(dir1->id), &dummy);
}
return 0;
}
static gboolean
remove_renamed_ids (gpointer key, gpointer value, gpointer user_data)
{
char *file_id = key;
GHashTable *deleted_ids = user_data;
if (g_hash_table_lookup (deleted_ids, file_id))
return TRUE;
else
return FALSE;
}
static GList *
calculate_send_fs_object_list (HttpTxTask *task, GHashTable *needed_file_pair)
{
GList *ret = NULL;
SeafBranch *local = NULL, *master = NULL;
SeafCommit *local_head = NULL, *master_head = NULL;
GList *ptr;
local = seaf_branch_manager_get_branch (seaf->branch_mgr, task->repo_id, "local");
if (!local) {
seaf_warning ("Branch local not found for repo %.8s.\n", task->repo_id);
goto out;
}
master = seaf_branch_manager_get_branch (seaf->branch_mgr, task->repo_id, "master");
if (!master) {
seaf_warning ("Branch master not found for repo %.8s.\n", task->repo_id);
goto out;
}
local_head = seaf_commit_manager_get_commit (seaf->commit_mgr,
task->repo_id, task->repo_version,
local->commit_id);
if (!local_head) {
seaf_warning ("Local head commit not found for repo %.8s.\n",
task->repo_id);
goto out;
}
master_head = seaf_commit_manager_get_commit (seaf->commit_mgr,
task->repo_id, task->repo_version,
master->commit_id);
if (!master_head) {
seaf_warning ("Master head commit not found for repo %.8s.\n",
task->repo_id);
goto out;
}
/* Diff won't traverse the root object itself. */
if (strcmp (local_head->root_id, master_head->root_id) != 0)
ret = g_list_prepend (ret, g_strdup(local_head->root_id));
CalcFsListData *data = g_new0(CalcFsListData, 1);
data->pret = &ret;
data->checked_objs = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, NULL);
data->needed_file_pair = needed_file_pair;
data->deleted_file_pair = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, g_free);
DiffOptions opts;
memset (&opts, 0, sizeof(opts));
memcpy (opts.store_id, task->repo_id, 36);
opts.version = task->repo_version;
opts.file_cb = collect_file_ids;
opts.dir_cb = collect_dir_ids;
opts.data = data;
const char *trees[2];
trees[0] = local_head->root_id;
trees[1] = master_head->root_id;
if (diff_trees (2, trees, &opts) < 0) {
seaf_warning ("Failed to diff local and master head for repo %.8s.\n",
task->repo_id);
for (ptr = ret; ptr; ptr = ptr->next)
g_free (ptr->data);
ret = NULL;
}
g_hash_table_foreach_remove (needed_file_pair, remove_renamed_ids,
data->deleted_file_pair);
g_hash_table_destroy (data->checked_objs);
g_hash_table_destroy (data->deleted_file_pair);
g_free (data);
out:
seaf_branch_unref (local);
seaf_branch_unref (master);
seaf_commit_unref (local_head);
seaf_commit_unref (master_head);
return ret;
}
#define ID_LIST_SEGMENT_N 1000
static int
upload_check_id_list_segment (HttpTxTask *task, Connection *conn, const char *url,
GList **send_id_list, GList **recv_id_list)
{
json_t *array;
json_error_t jerror;
char *obj_id;
int n_sent = 0;
char *data = NULL;
int len;
CURL *curl;
int status;
char *rsp_content = NULL;
gint64 rsp_size;
int ret = 0;
/* Convert object id list to JSON format. */
array = json_array ();
while (*send_id_list != NULL) {
obj_id = (*send_id_list)->data;
json_array_append_new (array, json_string(obj_id));
*send_id_list = g_list_delete_link (*send_id_list, *send_id_list);
g_free (obj_id);
if (++n_sent >= ID_LIST_SEGMENT_N)
break;
}
seaf_debug ("Check %d ids for %s:%s.\n",
n_sent, task->host, task->repo_id);
data = json_dumps (array, 0);
len = strlen(data);
json_decref (array);
/* Send fs object id list. */
curl = conn->curl;
int curl_error;
if (http_post (curl, url, task->token,
data, len,
&status, &rsp_content, &rsp_size, TRUE, HTTP_TIMEOUT_SEC, &curl_error) < 0) {
conn->release = TRUE;
handle_curl_errors (task, curl_error);
ret = -1;
goto out;
}
if (status != HTTP_OK) {
seaf_warning ("Bad response code for POST %s: %d.\n", url, status);
handle_http_errors (task, status);
ret = -1;
goto out;
}
/* Process needed object id list. */
array = json_loadb (rsp_content, rsp_size, 0, &jerror);
if (!array) {
seaf_warning ("Invalid JSON response from the server: %s.\n", jerror.text);
task->error = HTTP_TASK_ERR_SERVER;
ret = -1;
goto out;
}
int i;
size_t n = json_array_size (array);
json_t *str;
seaf_debug ("%lu objects or blocks are needed for %s:%s.\n",
n, task->host, task->repo_id);
for (i = 0; i < n; ++i) {
str = json_array_get (array, i);
if (!str) {
seaf_warning ("Invalid JSON response from the server.\n");
json_decref (array);
ret = -1;
goto out;
}
*recv_id_list = g_list_prepend (*recv_id_list, g_strdup(json_string_value(str)));
}
json_decref (array);
out:
curl_easy_reset (curl);
g_free (data);
g_free (rsp_content);
return ret;
}
#define MAX_OBJECT_PACK_SIZE (1 << 20) /* 1MB */
struct ObjectHeader {
char obj_id[40];
guint32 obj_size;
guint8 object[0];
} __attribute__((__packed__));
typedef struct ObjectHeader ObjectHeader;
static int
send_fs_objects (HttpTxTask *task, Connection *conn, GList **send_fs_list)
{
struct evbuffer *buf;
ObjectHeader hdr;
char *obj_id;
char *data;
int len;
int total_size;
unsigned char *package;
CURL *curl;
char *url = NULL;
int status;
int ret = 0;
int n_sent = 0;
buf = evbuffer_new ();
curl = conn->curl;
while (*send_fs_list != NULL) {
obj_id = (*send_fs_list)->data;
if (seaf_obj_store_read_obj (seaf->fs_mgr->obj_store,
task->repo_id, task->repo_version,
obj_id, (void **)&data, &len) < 0) {
seaf_warning ("Failed to read fs object %s in repo %s.\n",
obj_id, task->repo_id);
task->error = HTTP_TASK_ERR_BAD_LOCAL_DATA;
ret = -1;
goto out;
}
++n_sent;
memcpy (hdr.obj_id, obj_id, 40);
hdr.obj_size = htonl (len);
evbuffer_add (buf, &hdr, sizeof(hdr));
evbuffer_add (buf, data, len);
g_free (data);
*send_fs_list = g_list_delete_link (*send_fs_list, *send_fs_list);
g_free (obj_id);
total_size = evbuffer_get_length (buf);
if (total_size >= MAX_OBJECT_PACK_SIZE)
break;
}
seaf_debug ("Sending %d fs objects for %s:%s.\n",
n_sent, task->host, task->repo_id);
package = evbuffer_pullup (buf, -1);
if (!task->use_fileserver_port)
url = g_strdup_printf ("%s/seafhttp/repo/%s/recv-fs/",
task->host, task->repo_id);
else
url = g_strdup_printf ("%s/repo/%s/recv-fs/",
task->host, task->repo_id);
int curl_error;
if (http_post (curl, url, task->token,
(char *)package, evbuffer_get_length(buf),
&status, NULL, NULL, TRUE, HTTP_TIMEOUT_SEC, &curl_error) < 0) {
conn->release = TRUE;
handle_curl_errors (task, curl_error);
ret = -1;
goto out;
}
if (status != HTTP_OK) {
seaf_warning ("Bad response code for POST %s: %d.\n", url, status);
handle_http_errors (task, status);
ret = -1;
}
out:
g_free (url);
evbuffer_free (buf);
curl_easy_reset (curl);
return ret;
}
#if 0
static void
add_to_block_list (GList **block_list, GHashTable *added_blocks, const char *block_id)
{
int dummy;
if (g_hash_table_lookup (added_blocks, block_id))
return;
*block_list = g_list_prepend (*block_list, g_strdup(block_id));
g_hash_table_insert (added_blocks, g_strdup(block_id), &dummy);
}
static int
add_one_file_to_block_list (const char *repo_id, int repo_version,
const char *path, unsigned char *sha1,
GList **block_list, GHashTable *added_blocks,
GHashTable *needed_fs)
{
char file_id1[41];
Seafile *f1 = NULL;
int i;
rawdata_to_hex (sha1, file_id1, 20);
if (!g_hash_table_lookup (needed_fs, file_id1))
return 0;
f1 = seaf_fs_manager_get_seafile (seaf->fs_mgr,
repo_id, repo_version,
file_id1);
if (!f1) {
seaf_warning ("Failed to get seafile object %s:%s. Path is %s.\n",
repo_id, file_id1, path);
return -1;
}
for (i = 0; i < f1->n_blocks; ++i)
add_to_block_list (block_list, added_blocks, f1->blk_sha1s[i]);
seafile_unref (f1);
return 0;
}
static int
diff_two_files_to_block_list (const char *repo_id, int repo_version,
const char *path,
unsigned char *sha1, unsigned char *old_sha1,
GList **block_list, GHashTable *added_blocks)
{
Seafile *f1 = NULL, *f2 = NULL;
char file_id1[41], file_id2[41];
int i;
rawdata_to_hex (sha1, file_id1, 20);
rawdata_to_hex (old_sha1, file_id2, 20);
f1 = seaf_fs_manager_get_seafile (seaf->fs_mgr,
repo_id, repo_version,
file_id1);
if (!f1) {
seaf_warning ("Failed to get seafile object %s:%s. Path is %s.\n",
repo_id, file_id1, path);
return -1;
}
f2 = seaf_fs_manager_get_seafile (seaf->fs_mgr,
repo_id, repo_version,
file_id2);
if (!f2) {
seafile_unref (f1);
seaf_warning ("Failed to get seafile object %s:%s. Path is %s.\n",
repo_id, file_id2, path);
return -1;
}
GHashTable *h = g_hash_table_new (g_str_hash, g_str_equal);
int dummy;
for (i = 0; i < f2->n_blocks; ++i)
g_hash_table_insert (h, f2->blk_sha1s[i], &dummy);
for (i = 0; i < f1->n_blocks; ++i)
if (!g_hash_table_lookup (h, f1->blk_sha1s[i]))
add_to_block_list (block_list, added_blocks, f1->blk_sha1s[i]);
seafile_unref (f1);
seafile_unref (f2);
g_hash_table_destroy (h);
return 0;
}
typedef struct ExpandAddedDirData {
char repo_id[41];
int repo_version;
GList **block_list;
GHashTable *added_blocks;
GHashTable *needed_fs;
} ExpandAddedDirData;
static gboolean
expand_dir_added_cb (SeafFSManager *mgr,
const char *path,
SeafDirent *dent,
void *user_data,
gboolean *stop)
{
ExpandAddedDirData *data = user_data;
unsigned char sha1[20];
if (S_ISREG(dent->mode)) {
hex_to_rawdata (dent->id, sha1, 20);
if (add_one_file_to_block_list (data->repo_id, data->repo_version,
dent->name, sha1,
data->block_list, data->added_blocks,
data->needed_fs) < 0)
return FALSE;
}
return TRUE;
}
static int
add_new_dir_to_block_list (const char *repo_id, int version,
const char *root, DiffEntry *de,
GList **block_list, GHashTable *added_blocks,
GHashTable *needed_fs)
{
ExpandAddedDirData data;
char obj_id[41];
memcpy (data.repo_id, repo_id, 40);
data.repo_version = version;
data.block_list = block_list;
data.added_blocks = added_blocks;
data.needed_fs = needed_fs;
rawdata_to_hex (de->sha1, obj_id, 20);
if (seaf_fs_manager_traverse_path (seaf->fs_mgr,
repo_id, version,
root,
de->name,
expand_dir_added_cb,
&data) < 0) {
return -1;
}
return 0;
}
static int
calculate_block_list_from_diff_results (HttpTxTask *task, const char *local_root,
GHashTable *needed_fs,
GList *results, GList **block_list)
{
GHashTable *added_blocks;
GList *ptr;
DiffEntry *de;
int ret = 0;
added_blocks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
for (ptr = results; ptr; ptr = ptr->next) {
de = ptr->data;
if (de->status == DIFF_STATUS_ADDED) {
ret = add_one_file_to_block_list (task->repo_id, task->repo_version,
de->name, de->sha1,
block_list, added_blocks,
needed_fs);
if (ret < 0)
break;
} else if (de->status == DIFF_STATUS_MODIFIED) {
ret = diff_two_files_to_block_list (task->repo_id, task->repo_version,
de->name, de->sha1, de->old_sha1,
block_list, added_blocks);
if (ret < 0)
break;
} else if (de->status == DIFF_STATUS_DIR_ADDED) {
ret = add_new_dir_to_block_list (task->repo_id, task->repo_version,
local_root, de,
block_list, added_blocks,
needed_fs);
if (ret < 0)
break;
}
}
g_hash_table_destroy (added_blocks);
return ret;
}
static int
calculate_block_list (HttpTxTask *task, GList **plist, GHashTable *needed_fs)
{
int ret = 0;
SeafBranch *local = NULL, *master = NULL;
SeafCommit *local_head = NULL, *master_head = NULL;
GList *results = NULL;
GList *block_list = NULL;
local = seaf_branch_manager_get_branch (seaf->branch_mgr, task->repo_id, "local");
if (!local) {
seaf_warning ("Branch local not found for repo %.8s.\n", task->repo_id);
ret = -1;
goto out;
}
master = seaf_branch_manager_get_branch (seaf->branch_mgr, task->repo_id, "master");
if (!master) {
seaf_warning ("Branch master not found for repo %.8s.\n", task->repo_id);
ret = -1;
goto out;
}
local_head = seaf_commit_manager_get_commit (seaf->commit_mgr,
task->repo_id, task->repo_version,
local->commit_id);
if (!local_head) {
seaf_warning ("Local head commit not found for repo %.8s.\n",
task->repo_id);
ret = -1;
goto out;
}
master_head = seaf_commit_manager_get_commit (seaf->commit_mgr,
task->repo_id, task->repo_version,
master->commit_id);
if (!master_head) {
seaf_warning ("Master head commit not found for repo %.8s.\n",
task->repo_id);
ret = -1;
goto out;
}
if (diff_commit_roots (task->repo_id, task->repo_version,
master_head->root_id, local_head->root_id,
&results, TRUE) < 0) {
seaf_warning ("Failed to diff local and master heads for repo %.8s.\n",
task->repo_id);
ret = -1;
goto out;
}
if (calculate_block_list_from_diff_results (task, local_head->root_id,
needed_fs,
results, &block_list) < 0) {
ret = -1;
goto out;
}
*plist = block_list;
out:
seaf_branch_unref (local);
seaf_branch_unref (master);
seaf_commit_unref (local_head);
seaf_commit_unref (master_head);
g_list_free_full (results, (GDestroyNotify)diff_entry_free);
if (ret < 0)
g_list_free_full (block_list, g_free);
return ret;
}
#endif /* 0 */
typedef struct FileUploadProgress {
char *server;
char *user;
guint64 uploaded;
guint64 total_upload;
} FileUploadProgress;
typedef struct FileUploadedInfo {
char *server;
char *user;
char *file_path;
} FileUploadedInfo;
typedef struct {
char block_id[41];
BlockHandle *block;
HttpTxTask *task;
FileUploadProgress *progress;
} SendBlockData;
static FileUploadedInfo *
file_uploaded_info_new (const char *server, const char *user,
const char *file_path)
{
FileUploadedInfo *info = g_new0 (FileUploadedInfo, 1);
info->server = g_strdup (server);
info->user = g_strdup (user);
info->file_path = g_strdup (file_path);
return info;
}
static void
file_uploaded_info_free (FileUploadedInfo *info)
{
if (!info)
return;
g_free (info->server);
g_free (info->user);
g_free (info->file_path);
g_free (info);
return;
}
static size_t
send_block_callback (void *ptr, size_t size, size_t nmemb, void *userp)
{
size_t realsize = size *nmemb;
SendBlockData *data = userp;
HttpTxTask *task = data->task;
int n;
if (task->state == HTTP_TASK_STATE_CANCELED || task->all_stop)
return CURL_READFUNC_ABORT;
n = seaf_block_manager_read_block (seaf->block_mgr,
data->block,
ptr, realsize);
if (n < 0) {
seaf_warning ("Failed to read block %s in repo %.8s.\n",
data->block_id, task->repo_id);
task->error = HTTP_TASK_ERR_BAD_LOCAL_DATA;
return CURL_READFUNC_ABORT;
}
/* Update global transferred bytes. */
g_atomic_int_add (&(seaf->sync_mgr->sent_bytes), n);
/* Update transferred bytes for this task */
g_atomic_int_add (&task->tx_bytes, n);
data->progress->uploaded += n;
/* If uploaded bytes exceeds the limit, wait until the counter
* is reset. We check the counter every 100 milliseconds, so we
* can waste up to 100 milliseconds without sending data after
* the counter is reset.
*/
while (1) {
gint sent = g_atomic_int_get(&(seaf->sync_mgr->sent_bytes));
if (seaf->sync_mgr->upload_limit > 0 &&
sent > seaf->sync_mgr->upload_limit)
/* 100 milliseconds */
g_usleep (100000);
else
break;
}
return n;
}
static int
send_block (HttpTxTask *task, Connection *conn,
const char *block_id, uint32_t size,
FileUploadProgress *progress)
{
CURL *curl;
char *url;
int status;
BlockHandle *block;
int ret = 0;
block = seaf_block_manager_open_block (seaf->block_mgr,
task->repo_id, task->repo_version,
block_id, BLOCK_READ);
if (!block) {
seaf_warning ("Failed to open block %s in repo %s.\n",
block_id, task->repo_id);
return -1;
}
SendBlockData data;
memset (&data, 0, sizeof(data));
memcpy (data.block_id, block_id, 40);
data.block = block;
data.task = task;
data.progress = progress;
curl = conn->curl;
if (!task->use_fileserver_port)
url = g_strdup_printf ("%s/seafhttp/repo/%s/block/%s",
task->host, task->repo_id, block_id);
else
url = g_strdup_printf ("%s/repo/%s/block/%s",
task->host, task->repo_id, block_id);
int curl_error;
if (http_put (curl, url, task->token,
NULL, size,
send_block_callback, &data,
&status, NULL, NULL, TRUE, &curl_error) < 0) {
if (task->state == HTTP_TASK_STATE_CANCELED)
goto out;
if (task->error == HTTP_TASK_OK) {
/* Only release connection when it's a network error */
conn->release = TRUE;
handle_curl_errors (task, curl_error);
}
ret = -1;
goto out;
}
if (status != HTTP_OK) {
seaf_warning ("Bad response code for PUT %s: %d.\n", url, status);
handle_http_errors (task, status);
ret = -1;
}
out:
g_free (url);
curl_easy_reset (curl);
seaf_block_manager_close_block (seaf->block_mgr, block);
seaf_block_manager_block_handle_free (seaf->block_mgr, block);
return ret;
}
gboolean
get_needed_block_list (Connection *conn, HttpTxTask *task,
char *url, Seafile *file, GList **block_list)
{
int i, j;
int check_num = 0;
json_t *check_list = json_array ();
char *check_list_str;
json_t *ret_list;
json_error_t jerror;
int status;
char *rsp_content = NULL;
gint64 rsp_size;
const char *block_id;
gboolean ret = TRUE;
int curl_error;
CURL *curl = conn->curl;
for (i = 0; i < file->n_blocks; i++) {
json_array_append_new (check_list, json_string (file->blk_sha1s[i]));
check_num++;
if (check_num == ID_LIST_SEGMENT_N || i == file->n_blocks - 1) {
check_list_str = json_dumps (check_list, JSON_COMPACT);
curl_error = 0;
if (http_post (curl, url, task->token,
check_list_str, strlen(check_list_str),
&status, &rsp_content, &rsp_size, TRUE, HTTP_TIMEOUT_SEC, &curl_error) < 0) {
conn->release = TRUE;
handle_curl_errors (task, curl_error);
g_free (check_list_str);
ret = FALSE;
goto out;
}
g_free (check_list_str);
if (status != HTTP_OK) {
seaf_warning ("Bad response code for POST %s: %d.\n", url, status);
handle_http_errors (task, status);
g_free (rsp_content);
ret = FALSE;
goto out;
}
ret_list = json_loadb (rsp_content, rsp_size, 0, &jerror);
if (!ret_list) {
seaf_warning ("Invalid JSON response from the server: %s.\n", jerror.text);
task->error = HTTP_TASK_ERR_SERVER;
g_free (rsp_content);
ret = FALSE;
goto out;
}
g_free (rsp_content);
for (j = 0; j < json_array_size (ret_list); j++) {
block_id = json_string_value (json_array_get (ret_list, j));
if (!block_id || strlen (block_id) != 40) {
seaf_warning ("Invalid block_id list returned from server. "
"Bad block id %s\n", block_id);
task->error = HTTP_TASK_ERR_SERVER;
json_decref (ret_list);
ret = FALSE;
goto out;
}
*block_list = g_list_prepend (*block_list, g_strdup (block_id));
}
json_decref (ret_list);
rsp_content = NULL;
check_num = 0;
json_array_clear (check_list);
}
}
out:
curl_easy_reset (curl);
json_decref (check_list);
return ret;
}
static int
upload_file (HttpTxTask *http_task, Connection *conn,
const char *file_path, guint64 file_size, GList *block_list)
{
HttpTxPriv *priv = seaf->http_tx_mgr->priv;
FileUploadProgress progress;
GHashTable *block_size_pair;
char *abs_path;
GList *iter;
char *block_id;
BlockMetadata *meta;
guint64 upload_size = 0;
int ret = 0;
abs_path = g_build_filename (http_task->repo_uname, file_path, NULL);
if (!block_list && file_size == 0) {
FileUploadedInfo *file_info = file_uploaded_info_new (http_task->server, http_task->user, abs_path);
pthread_mutex_lock (&priv->progress_lock);
g_queue_push_head (priv->uploaded_files, file_info);
if (priv->uploaded_files->length > MAX_GET_FINISHED_FILES)
file_uploaded_info_free (g_queue_pop_tail (priv->uploaded_files));
pthread_mutex_unlock (&priv->progress_lock);
g_free (abs_path);
return 0;
} else if (!block_list) {
g_free (abs_path);
return 0;
}
progress.server = http_task->server;
progress.user = http_task->user;
progress.uploaded = 0;
progress.total_upload = file_size;
block_size_pair = g_hash_table_new (g_str_hash, g_str_equal);
// Collect file total upload size
for (iter = block_list; iter; iter = iter->next) {
block_id = iter->data;
meta = seaf_block_manager_stat_block (seaf->block_mgr,
http_task->repo_id,
http_task->repo_version,
block_id);
if (!meta) {
seaf_warning ("Failed to stat block %s for file %s in repo %s.\n",
block_id, file_path, http_task->repo_id);
http_task->error = HTTP_TASK_ERR_BAD_LOCAL_DATA;
ret = -1;
goto out;
}
upload_size += meta->size;
g_hash_table_replace (block_size_pair, block_id, (void *)meta->size);
g_free (meta);
}
progress.uploaded = file_size - upload_size;
// Update upload progress info
pthread_mutex_lock (&priv->progress_lock);
g_hash_table_replace (priv->uploading_files, g_strdup (abs_path), &progress);
pthread_mutex_unlock (&priv->progress_lock);
int n = 0;
for (iter = block_list; iter; iter = iter->next) {
block_id = iter->data;
if (send_block (http_task, conn, block_id,
(uint32_t)g_hash_table_lookup (block_size_pair, block_id),
&progress) < 0) {
ret = -1;
break;
}
++n;
}
seaf_debug ("Uploaded file %s, %d blocks.\n", file_path, n);
// Update upload progress info
pthread_mutex_lock (&priv->progress_lock);
g_hash_table_remove (priv->uploading_files, abs_path);
if (ret == 0) {
FileUploadedInfo *file_info = file_uploaded_info_new (http_task->server, http_task->user, abs_path);
g_queue_push_head (priv->uploaded_files, file_info);
if (priv->uploaded_files->length > MAX_GET_FINISHED_FILES)
file_uploaded_info_free (g_queue_pop_tail (priv->uploaded_files));
}
pthread_mutex_unlock (&priv->progress_lock);
out:
g_hash_table_destroy (block_size_pair);
g_free (abs_path);
return ret;
}
typedef struct FileUploadData {
HttpTxTask *http_task;
char *check_block_url;
ConnectionPool *cpool;
GAsyncQueue *finished_tasks;
} FileUploadData;
typedef struct FileUploadTask {
char *file_id;
char *file_path;
int result;
} FileUploadTask;
static void
upload_file_thread_func (gpointer data, gpointer user_data)
{
FileUploadTask *task = data;
FileUploadData *tx_data = user_data;
HttpTxTask *http_task = tx_data->http_task;
Connection *conn;
Seafile *file = NULL;
GList *block_list = NULL;
int ret = 0;
conn = connection_pool_get_connection (tx_data->cpool);
if (!conn) {
seaf_warning ("Failed to get connection to host %s.\n", http_task->host);
ret = -1;
http_task->error = HTTP_TASK_ERR_NOT_ENOUGH_MEMORY;
goto out;
}
file = seaf_fs_manager_get_seafile (seaf->fs_mgr,
http_task->repo_id,
http_task->repo_version,
task->file_id);
if (!file) {
seaf_warning ("Failed to get file %s in repo %.8s.\n",
task->file_id, http_task->repo_id);
http_task->error = HTTP_TASK_ERR_BAD_LOCAL_DATA;
ret = -1;
goto out;
}
if (!get_needed_block_list (conn, http_task,
tx_data->check_block_url, file,
&block_list)) {
seaf_warning ("Failed to get needed block list for file %s\n", task->file_path);
ret = -1;
goto out;
}
ret = upload_file (http_task, conn, task->file_path, file->file_size, block_list);
g_list_free_full (block_list, (GDestroyNotify)g_free);
out:
task->result = ret;
g_async_queue_push (tx_data->finished_tasks, task);
if (file)
seafile_unref (file);
if (conn)
connection_pool_return_connection (tx_data->cpool, conn);
}
#define DEFAULT_UPLOAD_BLOCK_THREADS 3
static int
multi_threaded_send_files (HttpTxTask *http_task, GHashTable *needed_file_pair)
{
HttpTxPriv *priv = seaf->http_tx_mgr->priv;
GThreadPool *tpool;
GAsyncQueue *finished_tasks;
GHashTable *pending_tasks;
char *check_block_url;
ConnectionPool *cpool;
GHashTableIter iter;
gpointer key;
gpointer value;
FileUploadTask *task;
int ret = 0;
// No file need to be uploaded, return directly
if (g_hash_table_size (needed_file_pair) == 0) {
return 0;
}
cpool = find_connection_pool (priv, http_task->host);
if (!cpool) {
seaf_warning ("Failed to create connection pool for host %s.\n", http_task->host);
http_task->error = HTTP_TASK_ERR_NOT_ENOUGH_MEMORY;
return -1;
}
if (!http_task->use_fileserver_port)
check_block_url = g_strdup_printf ("%s/seafhttp/repo/%s/check-blocks/",
http_task->host, http_task->repo_id);
else
check_block_url = g_strdup_printf ("%s/repo/%s/check-blocks/",
http_task->host, http_task->repo_id);
finished_tasks = g_async_queue_new ();
FileUploadData data;
data.http_task = http_task;
data.finished_tasks = finished_tasks;
data.check_block_url = check_block_url;
data.cpool = cpool;
tpool = g_thread_pool_new (upload_file_thread_func, &data,
DEFAULT_UPLOAD_BLOCK_THREADS, FALSE, NULL);
pending_tasks = g_hash_table_new_full (g_str_hash, g_str_equal,
NULL, (GDestroyNotify)g_free);
g_hash_table_iter_init (&iter, needed_file_pair);
while (g_hash_table_iter_next (&iter, &key, &value)) {
task = g_new0 (FileUploadTask, 1);
task->file_id = (char *)key;
task->file_path = (char *)value;
g_hash_table_insert (pending_tasks, task->file_id, task);
g_thread_pool_push (tpool, task, NULL);
}
while ((task = g_async_queue_pop (finished_tasks)) != NULL) {
if (task->result < 0 || http_task->state == HTTP_TASK_STATE_CANCELED) {
ret = task->result;
http_task->all_stop = TRUE;
break;
}
g_hash_table_remove (pending_tasks, task->file_id);
if (g_hash_table_size(pending_tasks) == 0)
break;
}
g_thread_pool_free (tpool, TRUE, TRUE);
g_hash_table_destroy (pending_tasks);
g_async_queue_unref (finished_tasks);
g_free (check_block_url);
return ret;
}
static void
notify_permission_error (HttpTxTask *task, const char *error_str)
{
HttpTxPriv *priv = seaf->http_tx_mgr->priv;
GMatchInfo *match_info;
char *path;
if (g_regex_match (priv->locked_error_regex, error_str, 0, &match_info)) {
path = g_match_info_fetch (match_info, 1);
task->unsyncable_path = path;
/* Set more accurate error. */
task->error = HTTP_TASK_ERR_FILE_LOCKED;
} else if (g_regex_match (priv->folder_perm_error_regex, error_str, 0, &match_info)) {
path = g_match_info_fetch (match_info, 1);
task->unsyncable_path = path;
task->error = HTTP_TASK_ERR_FOLDER_PERM_DENIED;
} else if (g_regex_match (priv->too_many_files_error_regex, error_str, 0, &match_info)) {
task->error = HTTP_TASK_ERR_TOO_MANY_FILES;
}
g_match_info_free (match_info);
}
static int
update_branch (HttpTxTask *task, Connection *conn)
{
CURL *curl;
char *url;
int status;
int ret = 0;
char *rsp_content;
char *rsp_content_str = NULL;
gint64 rsp_size;
curl = conn->curl;
if (!task->use_fileserver_port)
url = g_strdup_printf ("%s/seafhttp/repo/%s/commit/HEAD/?head=%s",
task->host, task->repo_id, task->head);
else
url = g_strdup_printf ("%s/repo/%s/commit/HEAD/?head=%s",
task->host, task->repo_id, task->head);
int curl_error;
if (http_put (curl, url, task->token,
NULL, 0,
NULL, NULL,
&status, &rsp_content, &rsp_size, TRUE, &curl_error) < 0) {
conn->release = TRUE;
handle_curl_errors (task, curl_error);
ret = -1;
goto out;
}
if (status != HTTP_OK) {
seaf_warning ("Bad response code for PUT %s: %d.\n", url, status);
handle_http_errors (task, status);
if (status == HTTP_FORBIDDEN || status == HTTP_BLOCK_MISSING) {
rsp_content_str = g_new0 (gchar, rsp_size + 1);
memcpy (rsp_content_str, rsp_content, rsp_size);
if (status == HTTP_FORBIDDEN) {
seaf_warning ("%s\n", rsp_content_str);
notify_permission_error (task, rsp_content_str);
} else if (status == HTTP_BLOCK_MISSING) {
seaf_warning ("Failed to upload files: %s\n", rsp_content_str);
task->error = HTTP_TASK_ERR_BLOCK_MISSING;
}
g_free (rsp_content_str);
}
ret = -1;
}
out:
g_free (url);
curl_easy_reset (curl);
return ret;
}
static int
update_master_branch (HttpTxTask *task)
{
SeafBranch *master = NULL, *local = NULL;
int ret = 0;
local = seaf_branch_manager_get_branch (seaf->branch_mgr,
task->repo_id,
"local");
if (!local) {
seaf_warning ("Failed to get local branch for repo %s.\n", task->repo_id);
return -1;
}
master = seaf_branch_manager_get_branch (seaf->branch_mgr,
task->repo_id,
"master");
if (!master) {
master = seaf_branch_new ("master", task->repo_id, task->head, 0);
if (seaf_branch_manager_add_branch (seaf->branch_mgr, master) < 0) {
ret = -1;
goto out;
}
} else {
seaf_branch_set_commit (master, task->head);
master->opid = local->opid;
if (seaf_branch_manager_update_branch (seaf->branch_mgr, master) < 0) {
ret = -1;
goto out;
}
}
/* The locally cache repo status from server need to be updated too.
* Otherwise sync-mgr will find the master head and server head are different,
* then start a download.
*/
seaf_repo_manager_set_repo_info_head_commit (seaf->repo_mgr,
task->repo_id,
task->head);
out:
seaf_branch_unref (master);
seaf_branch_unref (local);
return ret;
}
static void
set_path_status_syncing (gpointer key, gpointer value, gpointer user_data)
{
HttpTxTask *task = user_data;
char *path = key;
int mode = (int)(long)value;
seaf_sync_manager_update_active_path (seaf->sync_mgr,
task->repo_id,
path,
mode,
SYNC_STATUS_SYNCING);
}
static void
set_path_status_synced (gpointer key, gpointer value, gpointer user_data)
{
HttpTxTask *task = user_data;
char *path = key;
int mode = (int)(long)value;
seaf_sync_manager_update_active_path (seaf->sync_mgr,
task->repo_id,
path,
mode,
SYNC_STATUS_SYNCED);
if (S_ISREG(mode)) {
file_cache_mgr_set_file_uploaded (seaf->file_cache_mgr,
task->repo_id, path, TRUE);
}
}
static void *
http_upload_thread (void *vdata)
{
HttpTxTask *task = vdata;
HttpTxPriv *priv = seaf->http_tx_mgr->priv;
ConnectionPool *pool;
Connection *conn = NULL;
char *url = NULL;
GList *send_fs_list = NULL, *needed_fs_list = NULL;
GHashTable *active_paths = NULL;
// file_id <-> file_path
GHashTable *need_upload_file_pair = NULL;
SeafBranch *local = seaf_branch_manager_get_branch (seaf->branch_mgr,
task->repo_id, "local");
if (!local) {
seaf_warning ("Failed to get branch local of repo %.8s.\n", task->repo_id);
task->error = HTTP_TASK_ERR_BAD_LOCAL_DATA;
return vdata;
}
memcpy (task->head, local->commit_id, 40);
seaf_branch_unref (local);
pool = find_connection_pool (priv, task->host);
if (!pool) {
seaf_warning ("Failed to create connection pool for host %s.\n", task->host);
task->error = HTTP_TASK_ERR_NOT_ENOUGH_MEMORY;
goto out;
}
conn = connection_pool_get_connection (pool);
if (!conn) {
seaf_warning ("Failed to get connection to host %s.\n", task->host);
task->error = HTTP_TASK_ERR_NOT_ENOUGH_MEMORY;
goto out;
}
seaf_message ("Upload with HTTP sync protocol version %d.\n",
task->protocol_version);
transition_state (task, task->state, HTTP_TASK_RT_STATE_CHECK);
gint64 delta = 0;
active_paths = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
if (calculate_upload_size_delta_and_active_paths (task, &delta, active_paths) < 0) {
seaf_warning ("Failed to calculate upload size delta for repo %s.\n",
task->repo_id);
task->error = HTTP_TASK_ERR_BAD_LOCAL_DATA;
goto out;
}
g_hash_table_foreach (active_paths, set_path_status_syncing, task);
if (check_permission (task, conn) < 0) {
seaf_warning ("Upload permission denied for repo %.8s on server %s.\n",
task->repo_id, task->host);
goto out;
}
if (check_quota (task, conn, delta) < 0) {
seaf_warning ("Not enough quota for repo %.8s on server %s.\n",
task->repo_id, task->host);
goto out;
}
if (task->state == HTTP_TASK_STATE_CANCELED)
goto out;
transition_state (task, task->state, HTTP_TASK_RT_STATE_COMMIT);
if (send_commit_object (task, conn) < 0) {
seaf_warning ("Failed to send head commit for repo %.8s.\n", task->repo_id);
goto out;
}
if (task->state == HTTP_TASK_STATE_CANCELED)
goto out;
transition_state (task, task->state, HTTP_TASK_RT_STATE_FS);
need_upload_file_pair = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
send_fs_list = calculate_send_fs_object_list (task, need_upload_file_pair);
if (!send_fs_list) {
seaf_warning ("Failed to calculate fs object list for repo %.8s.\n",
task->repo_id);
task->error = HTTP_TASK_ERR_BAD_LOCAL_DATA;
goto out;
}
if (!task->use_fileserver_port)
url = g_strdup_printf ("%s/seafhttp/repo/%s/check-fs/",
task->host, task->repo_id);
else
url = g_strdup_printf ("%s/repo/%s/check-fs/",
task->host, task->repo_id);
while (send_fs_list != NULL) {
if (upload_check_id_list_segment (task, conn, url,
&send_fs_list, &needed_fs_list) < 0) {
seaf_warning ("Failed to check fs list for repo %.8s.\n", task->repo_id);
goto out;
}
if (task->state == HTTP_TASK_STATE_CANCELED)
goto out;
}
g_free (url);
url = NULL;
while (needed_fs_list != NULL) {
if (send_fs_objects (task, conn, &needed_fs_list) < 0) {
seaf_warning ("Failed to send fs objects for repo %.8s.\n", task->repo_id);
goto out;
}
if (task->state == HTTP_TASK_STATE_CANCELED)
goto out;
}
transition_state (task, task->state, HTTP_TASK_RT_STATE_BLOCK);
if (multi_threaded_send_files(task, need_upload_file_pair) < 0 ||
task->state == HTTP_TASK_STATE_CANCELED)
goto out;
transition_state (task, task->state, HTTP_TASK_RT_STATE_UPDATE_BRANCH);
if (update_branch (task, conn) < 0) {
seaf_warning ("Failed to update branch of repo %.8s.\n", task->repo_id);
goto out;
}
/* After successful upload, the cached 'master' branch should be updated to
* the head commit of 'local' branch.
*/
if (update_master_branch (task) < 0) {
task->error = HTTP_TASK_ERR_BAD_LOCAL_DATA;
goto out;
}
if (active_paths != NULL)
g_hash_table_foreach (active_paths, set_path_status_synced, task);
out:
string_list_free (send_fs_list);
string_list_free (needed_fs_list);
if (active_paths)
g_hash_table_destroy (active_paths);
if (need_upload_file_pair)
g_hash_table_destroy (need_upload_file_pair);
connection_pool_return_connection (pool, conn);
return vdata;
}
static void
http_upload_done (void *vdata)
{
HttpTxTask *task = vdata;
if (task->error != HTTP_TASK_OK)
transition_state (task, HTTP_TASK_STATE_ERROR, HTTP_TASK_RT_STATE_FINISHED);
else if (task->state == HTTP_TASK_STATE_CANCELED)
transition_state (task, task->state, HTTP_TASK_RT_STATE_FINISHED);
else
transition_state (task, HTTP_TASK_STATE_FINISHED, HTTP_TASK_RT_STATE_FINISHED);
}
/* Download */
static void *http_download_thread (void *vdata);
static void http_download_done (void *vdata);
/* static void notify_conflict (CEvent *event, void *data); */
int
http_tx_manager_add_download (HttpTxManager *manager,
const char *repo_id,
int repo_version,
const char *server,
const char *user,
const char *host,
const char *token,
const char *server_head_id,
gboolean is_clone,
int protocol_version,
gboolean use_fileserver_port,
GError **error)
{
HttpTxTask *task;
if (!repo_id || !token || !host || !server_head_id) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Empty argument(s)");
return -1;
}
if (!is_clone) {
if (!seaf_repo_manager_repo_exists (seaf->repo_mgr, repo_id)) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Repo not found");
return -1;
}
}
clean_tasks_for_repo (manager, repo_id);
task = http_tx_task_new (manager, repo_id, repo_version,
HTTP_TASK_TYPE_DOWNLOAD, is_clone,
host, token);
memcpy (task->head, server_head_id, 40);
task->protocol_version = protocol_version;
task->state = HTTP_TASK_STATE_NORMAL;
task->use_fileserver_port = use_fileserver_port;
task->server = g_strdup (server);
task->user = g_strdup (user);
g_hash_table_insert (manager->priv->download_tasks,
g_strdup(repo_id),
task);
if (seaf_job_manager_schedule_job (seaf->job_mgr,
http_download_thread,
http_download_done,
task) < 0) {
g_hash_table_remove (manager->priv->download_tasks, repo_id);
return -1;
}
return 0;
}
static int
get_commit_object (HttpTxTask *task, Connection *conn)
{
CURL *curl;
char *url;
int status;
char *rsp_content = NULL;
gint64 rsp_size;
int ret = 0;
curl = conn->curl;
if (!task->use_fileserver_port)
url = g_strdup_printf ("%s/seafhttp/repo/%s/commit/%s",
task->host, task->repo_id, task->head);
else
url = g_strdup_printf ("%s/repo/%s/commit/%s",
task->host, task->repo_id, task->head);
int curl_error;
if (http_get (curl, url, task->token, &status,
&rsp_content, &rsp_size,
NULL, NULL, TRUE, HTTP_TIMEOUT_SEC, &curl_error) < 0) {
conn->release = TRUE;
handle_curl_errors (task, curl_error);
ret = -1;
goto out;
}
if (status != HTTP_OK) {
seaf_warning ("Bad response code for GET %s: %d.\n", url, status);
handle_http_errors (task, status);
ret = -1;
goto out;
}
int rc = seaf_obj_store_write_obj (seaf->commit_mgr->obj_store,
task->repo_id, task->repo_version,
task->head,
rsp_content,
rsp_size,
FALSE);
if (rc < 0) {
seaf_warning ("Failed to save commit %s in repo %.8s.\n",
task->head, task->repo_id);
task->error = HTTP_TASK_ERR_WRITE_LOCAL_DATA;
ret = -1;
}
out:
g_free (url);
g_free (rsp_content);
curl_easy_reset (curl);
return ret;
}
static int
get_needed_fs_id_list (HttpTxTask *task, Connection *conn, GList **fs_id_list)
{
SeafBranch *master;
CURL *curl;
char *url = NULL;
int status;
char *rsp_content = NULL;
gint64 rsp_size;
int ret = 0;
json_t *array;
json_error_t jerror;
const char *obj_id;
const char *url_prefix = (task->use_fileserver_port) ? "" : "seafhttp/";
if (!task->is_clone) {
master = seaf_branch_manager_get_branch (seaf->branch_mgr,
task->repo_id,
"master");
if (!master) {
seaf_warning ("Failed to get branch master for repo %.8s.\n",
task->repo_id);
return -1;
}
url = g_strdup_printf ("%s/%srepo/%s/fs-id-list/"
"?server-head=%s&client-head=%s&dir-only=1",
task->host, url_prefix, task->repo_id,
task->head, master->commit_id);
seaf_branch_unref (master);
} else {
url = g_strdup_printf ("%s/%srepo/%s/fs-id-list/?server-head=%s&dir-only=1",
task->host, url_prefix, task->repo_id, task->head);
}
curl = conn->curl;
int curl_error;
if (http_get (curl, url, task->token, &status,
&rsp_content, &rsp_size,
NULL, NULL, FALSE, HTTP_TIMEOUT_SEC, &curl_error) < 0) {
conn->release = TRUE;
handle_curl_errors (task, curl_error);
ret = -1;
goto out;
}
if (status != HTTP_OK) {
seaf_warning ("Bad response code for GET %s: %d.\n", url, status);
handle_http_errors (task, status);
ret = -1;
goto out;
}
array = json_loadb (rsp_content, rsp_size, 0, &jerror);
if (!array) {
seaf_warning ("Invalid JSON response from the server: %s.\n", jerror.text);
task->error = HTTP_TASK_ERR_SERVER;
ret = -1;
goto out;
}
int i;
size_t n = json_array_size (array);
json_t *str;
seaf_debug ("Received fs object list size %lu from %s:%s.\n",
n, task->host, task->repo_id);
task->n_fs_objs = (int)n;
GHashTable *checked_objs = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, NULL);
for (i = 0; i < n; ++i) {
str = json_array_get (array, i);
if (!str) {
seaf_warning ("Invalid JSON response from the server.\n");
json_decref (array);
string_list_free (*fs_id_list);
ret = -1;
goto out;
}
obj_id = json_string_value(str);
if (g_hash_table_lookup (checked_objs, obj_id)) {
++(task->done_fs_objs);
continue;
}
char *key = g_strdup(obj_id);
g_hash_table_replace (checked_objs, key, key);
if (!seaf_obj_store_obj_exists (seaf->fs_mgr->obj_store,
task->repo_id, task->repo_version,
obj_id)) {
*fs_id_list = g_list_prepend (*fs_id_list, g_strdup(obj_id));
} else if (task->is_clone) {
gboolean io_error = FALSE;
gboolean sound;
sound = seaf_fs_manager_verify_object (seaf->fs_mgr,
task->repo_id, task->repo_version,
obj_id, FALSE, &io_error);
if (!sound && !io_error) {
*fs_id_list = g_list_prepend (*fs_id_list, g_strdup(obj_id));
} else {
++(task->done_fs_objs);
}
} else {
++(task->done_fs_objs);
}
}
json_decref (array);
g_hash_table_destroy (checked_objs);
out:
g_free (url);
g_free (rsp_content);
curl_easy_reset (curl);
return ret;
}
#define GET_FS_OBJECT_N 100
static int
get_fs_objects (HttpTxTask *task, Connection *conn, GList **fs_list)
{
json_t *array;
char *obj_id;
int n_sent = 0;
char *data = NULL;
int len;
CURL *curl;
char *url = NULL;
int status;
char *rsp_content = NULL;
gint64 rsp_size;
int ret = 0;
GHashTable *requested;
/* Convert object id list to JSON format. */
requested = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
array = json_array ();
while (*fs_list != NULL) {
obj_id = (*fs_list)->data;
json_array_append_new (array, json_string(obj_id));
*fs_list = g_list_delete_link (*fs_list, *fs_list);
g_hash_table_replace (requested, obj_id, obj_id);
if (++n_sent >= GET_FS_OBJECT_N)
break;
}
seaf_debug ("Requesting %d fs objects from %s:%s.\n",
n_sent, task->host, task->repo_id);
data = json_dumps (array, 0);
len = strlen(data);
json_decref (array);
/* Send fs object id list. */
curl = conn->curl;
if (!task->use_fileserver_port)
url = g_strdup_printf ("%s/seafhttp/repo/%s/pack-fs/", task->host, task->repo_id);
else
url = g_strdup_printf ("%s/repo/%s/pack-fs/", task->host, task->repo_id);
int curl_error;
if (http_post (curl, url, task->token,
data, len,
&status, &rsp_content, &rsp_size, TRUE, HTTP_TIMEOUT_SEC, &curl_error) < 0) {
conn->release = TRUE;
handle_curl_errors (task, curl_error);
ret = -1;
goto out;
}
if (status != HTTP_OK) {
seaf_warning ("Bad response code for POST %s: %d.\n", url, status);
handle_http_errors (task, status);
ret = -1;
goto out;
}
/* Save received fs objects. */
int n_recv = 0;
char *p = rsp_content;
ObjectHeader *hdr = (ObjectHeader *)p;
char recv_obj_id[41];
int n = 0;
int size;
int rc;
while (n < rsp_size) {
memcpy (recv_obj_id, hdr->obj_id, 40);
recv_obj_id[40] = 0;
size = ntohl (hdr->obj_size);
if (n + sizeof(ObjectHeader) + size > rsp_size) {
seaf_warning ("Incomplete object package received for repo %.8s.\n",
task->repo_id);
task->error = HTTP_TASK_ERR_SERVER;
ret = -1;
goto out;
}
++n_recv;
rc = seaf_obj_store_write_obj (seaf->fs_mgr->obj_store,
task->repo_id, task->repo_version,
recv_obj_id,
hdr->object,
size, FALSE);
if (rc < 0) {
seaf_warning ("Failed to write fs object %s in repo %.8s.\n",
recv_obj_id, task->repo_id);
task->error = HTTP_TASK_ERR_WRITE_LOCAL_DATA;
ret = -1;
goto out;
}
g_hash_table_remove (requested, recv_obj_id);
++(task->done_fs_objs);
p += (sizeof(ObjectHeader) + size);
n += (sizeof(ObjectHeader) + size);
hdr = (ObjectHeader *)p;
}
seaf_debug ("Received %d fs objects from %s:%s.\n",
n_recv, task->host, task->repo_id);
/* The server may not return all the objects we requested.
* So we need to add back the remaining object ids into fs_list.
*/
GHashTableIter iter;
gpointer key, value;
g_hash_table_iter_init (&iter, requested);
while (g_hash_table_iter_next (&iter, &key, &value)) {
obj_id = key;
*fs_list = g_list_prepend (*fs_list, g_strdup(obj_id));
}
g_hash_table_destroy (requested);
out:
g_free (url);
g_free (data);
g_free (rsp_content);
curl_easy_reset (curl);
return ret;
}
int
http_tx_manager_get_block (HttpTxManager *mgr, const char *host,
gboolean use_fileserver_port, const char *token,
const char *repo_id, const char *block_id,
HttpRecvCallback get_blk_cb, void *user_data,
int *http_status)
{
HttpTxPriv *priv = mgr->priv;
ConnectionPool *pool;
Connection *conn;
CURL *curl;
char *url;
int ret = 0;
pool = find_connection_pool (priv, host);
if (!pool) {
seaf_warning ("Failed to create connection pool for host %s.\n", host);
return -1;
}
conn = connection_pool_get_connection (pool);
if (!conn) {
seaf_warning ("Failed to get connection to host %s.\n", host);
return -1;
}
curl = conn->curl;
if (!use_fileserver_port)
url = g_strdup_printf ("%s/seafhttp/repo/%s/block/%s",
host, repo_id, block_id);
else
url = g_strdup_printf ("%s/repo/%s/block/%s",
host, repo_id, block_id);
if (http_get (curl, url, token, http_status, NULL, NULL,
get_blk_cb, user_data, TRUE, HTTP_TIMEOUT_SEC, NULL) < 0) {
conn->release = TRUE;
ret = -1;
goto out;
}
if (*http_status != HTTP_OK) {
seaf_warning ("Bad response code for GET %s: %d.\n", url, *http_status);
ret = -1;
}
out:
g_free (url);
connection_pool_return_connection (pool, conn);
return ret;
}
int
http_tx_manager_get_fs_object (HttpTxManager *mgr, const char *host,
gboolean use_fileserver_port, const char *token,
const char *repo_id, const char *obj_id,
const char *file_path)
{
HttpTxPriv *priv = mgr->priv;
ConnectionPool *pool;
Connection *conn;
json_t *array;
char *data = NULL;
int len;
CURL *curl;
char *url = NULL;
int status;
char *rsp_content = NULL;
gint64 rsp_size;
int ret = 0;
pool = find_connection_pool (priv, host);
if (!pool) {
seaf_warning ("Failed to create connection pool for host %s.\n", host);
return -1;
}
conn = connection_pool_get_connection (pool);
if (!conn) {
seaf_warning ("Failed to get connection to host %s.\n", host);
return -1;
}
/* Convert object id list to JSON format. */
array = json_array ();
json_array_append_new (array, json_string(obj_id));
data = json_dumps (array, 0);
len = strlen(data);
json_decref (array);
/* Send fs object id list. */
curl = conn->curl;
char *esc_path = g_uri_escape_string (file_path, NULL, FALSE);
if (!use_fileserver_port)
url = g_strdup_printf ("%s/seafhttp/repo/%s/pack-fs/?path=%s", host, repo_id, esc_path);
else
url = g_strdup_printf ("%s/repo/%s/pack-fs/?path=%s", host, repo_id, esc_path);
g_free (esc_path);
if (http_post (curl, url, token,
data, len,
&status, &rsp_content, &rsp_size, TRUE, HTTP_TIMEOUT_SEC, NULL) < 0) {
conn->release = TRUE;
ret = -1;
goto out;
}
if (status != HTTP_OK) {
seaf_warning ("Bad response code for POST %s: %d.\n", url, status);
ret = -1;
goto out;
}
/* Save received fs objects. */
char *p = rsp_content;
ObjectHeader *hdr = (ObjectHeader *)p;
char recv_obj_id[41];
int n = 0;
int size;
int rc;
while (n < rsp_size) {
memcpy (recv_obj_id, hdr->obj_id, 40);
recv_obj_id[40] = 0;
size = ntohl (hdr->obj_size);
if (n + sizeof(ObjectHeader) + size > rsp_size) {
seaf_warning ("Incomplete object package received for repo %.8s.\n",
repo_id);
ret = -1;
goto out;
}
rc = seaf_obj_store_write_obj (seaf->fs_mgr->obj_store,
repo_id, 1,
recv_obj_id,
hdr->object,
size, FALSE);
if (rc < 0) {
seaf_warning ("Failed to write fs object %s in repo %.8s.\n",
recv_obj_id, repo_id);
ret = -1;
goto out;
}
p += (sizeof(ObjectHeader) + size);
n += (sizeof(ObjectHeader) + size);
hdr = (ObjectHeader *)p;
}
out:
g_free (url);
g_free (data);
g_free (rsp_content);
connection_pool_return_connection (pool, conn);
return ret;
}
int
http_tx_manager_get_file (HttpTxManager *mgr, const char *host,
gboolean use_fileserver_port, const char *token,
const char *repo_id,
const char *path,
gint64 block_offset,
HttpRecvCallback get_blk_cb, void *user_data,
int *http_status)
{
HttpTxPriv *priv = mgr->priv;
ConnectionPool *pool;
Connection *conn;
CURL *curl;
char *url;
int ret = 0;
pool = find_connection_pool (priv, host);
if (!pool) {
seaf_warning ("Failed to create connection pool for host %s.\n", host);
return -1;
}
conn = connection_pool_get_connection (pool);
if (!conn) {
seaf_warning ("Failed to get connection to host %s.\n", host);
return -1;
}
curl = conn->curl;
char *esc_path = g_uri_escape_string(path, NULL, FALSE);
if (!use_fileserver_port)
url = g_strdup_printf ("%s/seafhttp/repo/%s/file/?path=%s&offset=%"G_GINT64_FORMAT"",
host, repo_id, esc_path, block_offset);
else
url = g_strdup_printf ("%s/repo/%s/file/?path=%s&offset=%"G_GINT64_FORMAT"",
host, repo_id, esc_path, block_offset);
g_free (esc_path);
if (http_get (curl, url, token, http_status, NULL, NULL,
get_blk_cb, user_data, TRUE, HTTP_TIMEOUT_SEC, NULL) < 0) {
conn->release = TRUE;
ret = -1;
goto out;
}
if (*http_status != HTTP_OK) {
seaf_warning ("Bad response code for GET %s: %d.\n", url, *http_status);
ret = -1;
}
out:
g_free (url);
connection_pool_return_connection (pool, conn);
return ret;
}
/* typedef struct { */
/* char block_id[41]; */
/* BlockHandle *block; */
/* HttpTxTask *task; */
/* } GetBlockData; */
/* static size_t */
/* get_block_callback (void *ptr, size_t size, size_t nmemb, void *userp) */
/* { */
/* size_t realsize = size *nmemb; */
/* SendBlockData *data = userp; */
/* HttpTxTask *task = data->task; */
/* size_t n; */
/* if (task->state == HTTP_TASK_STATE_CANCELED || task->all_stop) */
/* return 0; */
/* n = seaf_block_manager_write_block (seaf->block_mgr, */
/* data->block, */
/* ptr, realsize); */
/* if (n < realsize) { */
/* seaf_warning ("Failed to write block %s in repo %.8s.\n", */
/* data->block_id, task->repo_id); */
/* task->error = HTTP_TASK_ERR_BAD_LOCAL_DATA; */
/* return n; */
/* } */
/* /\* Update global transferred bytes. *\/ */
/* g_atomic_int_add (&(seaf->sync_mgr->recv_bytes), n); */
/* /\* Update transferred bytes for this task *\/ */
/* g_atomic_int_add (&task->tx_bytes, n); */
/* /\* If uploaded bytes exceeds the limit, wait until the counter */
/* * is reset. We check the counter every 100 milliseconds, so we */
/* * can waste up to 100 milliseconds without sending data after */
/* * the counter is reset. */
/* *\/ */
/* while (1) { */
/* gint sent = g_atomic_int_get(&(seaf->sync_mgr->recv_bytes)); */
/* if (seaf->sync_mgr->download_limit > 0 && */
/* sent > seaf->sync_mgr->download_limit) */
/* /\* 100 milliseconds *\/ */
/* g_usleep (100000); */
/* else */
/* break; */
/* } */
/* return n; */
/* } */
/* int */
/* get_block (HttpTxTask *task, Connection *conn, const char *block_id) */
/* { */
/* CURL *curl; */
/* char *url; */
/* int status; */
/* BlockHandle *block; */
/* int ret = 0; */
/* int *pcnt; */
/* block = seaf_block_manager_open_block (seaf->block_mgr, */
/* task->repo_id, task->repo_version, */
/* block_id, BLOCK_WRITE); */
/* if (!block) { */
/* seaf_warning ("Failed to open block %s in repo %.8s.\n", */
/* block_id, task->repo_id); */
/* return -1; */
/* } */
/* GetBlockData data; */
/* memcpy (data.block_id, block_id, 40); */
/* data.block = block; */
/* data.task = task; */
/* curl = conn->curl; */
/* if (!task->use_fileserver_port) */
/* url = g_strdup_printf ("%s/seafhttp/repo/%s/block/%s", */
/* task->host, task->repo_id, block_id); */
/* else */
/* url = g_strdup_printf ("%s/repo/%s/block/%s", */
/* task->host, task->repo_id, block_id); */
/* if (http_get (curl, url, task->token, &status, NULL, NULL, */
/* get_block_callback, &data, TRUE) < 0) { */
/* if (task->state == HTTP_TASK_STATE_CANCELED) */
/* goto error; */
/* if (task->error == HTTP_TASK_OK) { */
/* /\* Only release the connection when it's a network error. *\/ */
/* conn->release = TRUE; */
/* task->error = HTTP_TASK_ERR_NET; */
/* } */
/* ret = -1; */
/* goto error; */
/* } */
/* if (status != HTTP_OK) { */
/* seaf_warning ("Bad response code for GET %s: %d.\n", url, status); */
/* handle_http_errors (task, status); */
/* ret = -1; */
/* goto error; */
/* } */
/* seaf_block_manager_close_block (seaf->block_mgr, block); */
/* pthread_mutex_lock (&task->ref_cnt_lock); */
/* /\* Don't overwrite the block if other thread already downloaded it. */
/* * Since we've locked ref_cnt_lock, we can be sure the block won't be removed. */
/* *\/ */
/* if (!seaf_block_manager_block_exists (seaf->block_mgr, */
/* task->repo_id, task->repo_version, */
/* block_id) && */
/* seaf_block_manager_commit_block (seaf->block_mgr, block) < 0) */
/* { */
/* seaf_warning ("Failed to commit block %s in repo %.8s.\n", */
/* block_id, task->repo_id); */
/* task->error = HTTP_TASK_ERR_WRITE_LOCAL_DATA; */
/* ret = -1; */
/* } */
/* if (ret == 0) { */
/* pcnt = g_hash_table_lookup (task->blk_ref_cnts, block_id); */
/* if (!pcnt) { */
/* pcnt = g_new0(int, 1); */
/* g_hash_table_insert (task->blk_ref_cnts, g_strdup(block_id), pcnt); */
/* } */
/* *pcnt += 1; */
/* } */
/* pthread_mutex_unlock (&task->ref_cnt_lock); */
/* seaf_block_manager_block_handle_free (seaf->block_mgr, block); */
/* g_free (url); */
/* return ret; */
/* error: */
/* g_free (url); */
/* seaf_block_manager_close_block (seaf->block_mgr, block); */
/* seaf_block_manager_block_handle_free (seaf->block_mgr, block); */
/* return ret; */
/* } */
/* int */
/* http_tx_task_download_file_blocks (HttpTxTask *task, const char *file_id) */
/* { */
/* Seafile *file; */
/* HttpTxPriv *priv = seaf->http_tx_mgr->priv; */
/* ConnectionPool *pool; */
/* Connection *conn; */
/* int ret = 0; */
/* file = seaf_fs_manager_get_seafile (seaf->fs_mgr, */
/* task->repo_id, */
/* task->repo_version, */
/* file_id); */
/* if (!file) { */
/* seaf_warning ("Failed to find seafile object %s in repo %.8s.\n", */
/* file_id, task->repo_id); */
/* return -1; */
/* } */
/* pool = find_connection_pool (priv, task->host); */
/* if (!pool) { */
/* seaf_warning ("Failed to create connection pool for host %s.\n", task->host); */
/* task->error = HTTP_TASK_ERR_NOT_ENOUGH_MEMORY; */
/* seafile_unref (file); */
/* return -1; */
/* } */
/* conn = connection_pool_get_connection (pool); */
/* if (!conn) { */
/* seaf_warning ("Failed to get connection to host %s.\n", task->host); */
/* task->error = HTTP_TASK_ERR_NOT_ENOUGH_MEMORY; */
/* seafile_unref (file); */
/* return -1; */
/* } */
/* int i; */
/* char *block_id; */
/* for (i = 0; i < file->n_blocks; ++i) { */
/* block_id = file->blk_sha1s[i]; */
/* ret = get_block (task, conn, block_id); */
/* if (ret < 0 || task->state == HTTP_TASK_STATE_CANCELED) */
/* break; */
/* } */
/* connection_pool_return_connection (pool, conn); */
/* seafile_unref (file); */
/* return ret; */
/* } */
static int
update_local_repo (HttpTxTask *task)
{
SeafRepo *repo = NULL;
SeafCommit *new_head;
SeafBranch *branch;
int ret = 0;
new_head = seaf_commit_manager_get_commit (seaf->commit_mgr,
task->repo_id,
task->repo_version,
task->head);
if (!new_head) {
seaf_warning ("Failed to get commit %s:%s.\n", task->repo_id, task->head);
task->error = HTTP_TASK_ERR_BAD_LOCAL_DATA;
return -1;
}
if (task->is_clone) {
/* If repo doesn't exist, create it.
* Note that branch doesn't exist either in this case.
*/
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, new_head->repo_id);
if (repo != NULL) {
seaf_repo_unref (repo);
goto out;
}
repo = seaf_repo_new (new_head->repo_id, NULL, NULL);
if (repo == NULL) {
/* create repo failed */
task->error = HTTP_TASK_ERR_NOT_ENOUGH_MEMORY;
ret = -1;
goto out;
}
repo->server = g_strdup (task->server);
repo->user = g_strdup (task->user);
seaf_repo_from_commit (repo, new_head);
/* If it's a new repo, create 'local' and 'master' branch */
branch = seaf_branch_new ("local", task->repo_id, task->head, 0);
seaf_branch_manager_add_branch (seaf->branch_mgr, branch);
/* Set repo head branch to local */
seaf_repo_set_head (repo, branch);
seaf_branch_unref (branch);
branch = seaf_branch_new ("master", task->repo_id, task->head, 0);
seaf_branch_manager_add_branch (seaf->branch_mgr, branch);
seaf_branch_unref (branch);
seaf_repo_manager_add_repo (seaf->repo_mgr, repo);
}
/* If repo already exists, update branches in sync-mgr. */
out:
seaf_commit_unref (new_head);
return ret;
}
static void *
http_download_thread (void *vdata)
{
HttpTxTask *task = vdata;
HttpTxPriv *priv = seaf->http_tx_mgr->priv;
ConnectionPool *pool;
Connection *conn = NULL;
GList *fs_id_list = NULL;
pool = find_connection_pool (priv, task->host);
if (!pool) {
seaf_warning ("Failed to create connection pool for host %s.\n", task->host);
task->error = HTTP_TASK_ERR_NOT_ENOUGH_MEMORY;
goto out;
}
conn = connection_pool_get_connection (pool);
if (!conn) {
seaf_warning ("failed to get connection to host %s.\n", task->host);
task->error = HTTP_TASK_ERR_NOT_ENOUGH_MEMORY;
goto out;
}
seaf_message ("Download with HTTP sync protocol version %d.\n",
task->protocol_version);
transition_state (task, task->state, HTTP_TASK_RT_STATE_CHECK);
if (check_permission (task, conn) < 0) {
seaf_warning ("Download permission denied for repo %.8s on server %s.\n",
task->repo_id, task->host);
goto out;
}
if (task->state == HTTP_TASK_STATE_CANCELED)
goto out;
transition_state (task, task->state, HTTP_TASK_RT_STATE_COMMIT);
if (get_commit_object (task, conn) < 0) {
seaf_warning ("Failed to get server head commit for repo %.8s on server %s.\n",
task->repo_id, task->host);
goto out;
}
if (task->state == HTTP_TASK_STATE_CANCELED)
goto out;
transition_state (task, task->state, HTTP_TASK_RT_STATE_FS);
if (get_needed_fs_id_list (task, conn, &fs_id_list) < 0) {
seaf_warning ("Failed to get fs id list for repo %.8s on server %s.\n",
task->repo_id, task->host);
goto out;
}
if (task->state == HTTP_TASK_STATE_CANCELED)
goto out;
while (fs_id_list != NULL) {
if (get_fs_objects (task, conn, &fs_id_list) < 0) {
seaf_warning ("Failed to get fs objects for repo %.8s on server %s.\n",
task->repo_id, task->host);
goto out;
}
if (task->state == HTTP_TASK_STATE_CANCELED)
goto out;
}
update_local_repo (task);
out:
connection_pool_return_connection (pool, conn);
string_list_free (fs_id_list);
return vdata;
}
static void
http_download_done (void *vdata)
{
HttpTxTask *task = vdata;
if (task->error != HTTP_TASK_OK)
transition_state (task, HTTP_TASK_STATE_ERROR, HTTP_TASK_RT_STATE_FINISHED);
else if (task->state == HTTP_TASK_STATE_CANCELED)
transition_state (task, task->state, HTTP_TASK_RT_STATE_FINISHED);
else
transition_state (task, HTTP_TASK_STATE_FINISHED, HTTP_TASK_RT_STATE_FINISHED);
}
#define SYNC_MOVE_TIMEOUT 600
int
synchronous_move (Connection *conn, const char *host, const char *api_token,
const char *repo_id1, const char *oldpath,
const char *repo_id2, const char *newpath)
{
CURL *curl = conn->curl;
char *dst_dir = NULL, *esc_oldpath = NULL;
char *url = NULL;
char *req_content = NULL;
int status;
char *rsp_content = NULL;
gint64 rsp_size;
int ret = 0;
dst_dir = g_path_get_dirname (newpath);
if (strcmp(dst_dir, ".") == 0) {
g_free (dst_dir);
dst_dir = g_strdup("");
}
esc_oldpath = g_uri_escape_string (oldpath, NULL, FALSE);
req_content = g_strdup_printf ("operation=move&dst_repo=%s&dst_dir=/%s",
repo_id2, dst_dir);
url = g_strdup_printf ("%s/api2/repos/%s/file/?p=/%s", host, repo_id1, esc_oldpath);
if (http_api_post (curl, url, api_token, req_content, strlen(req_content),
&status, &rsp_content, &rsp_size,
TRUE, SYNC_MOVE_TIMEOUT, NULL) < 0) {
conn->release = TRUE;
ret = -1;
goto out;
}
if (status != HTTP_MOVED_PERMANENTLY) {
seaf_warning ("Bad response code for POST %s: %d, response error: %s.\n", url, status, (rsp_content ? rsp_content : "no response body"));
ret = -1;
}
out:
g_free (dst_dir);
g_free (esc_oldpath);
g_free (req_content);
g_free (url);
return ret;
}
int
asynchronous_move (Connection *conn, const char *host, const char *api_token,
const char *repo_id1, const char *oldpath,
const char *repo_id2, const char *newpath,
gboolean is_file, gboolean *no_api)
{
CURL *curl = conn->curl;
char *src_dir = NULL, *src_filename = NULL, *dst_dir = NULL;
char *url = NULL;
char *req_content = NULL;
int status;
char *rsp_content = NULL;
gint64 rsp_size;
int ret = 0;
json_t *obj = NULL;
json_t *req_obj = NULL;
json_t *array = NULL;
json_error_t jerror;
src_dir = g_path_get_dirname (oldpath);
if (strcmp(src_dir, ".") == 0) {
g_free (src_dir);
src_dir = g_strdup("/");
}
src_filename = g_path_get_basename (oldpath);
dst_dir = g_path_get_dirname (newpath);
if (strcmp(dst_dir, ".") == 0) {
g_free (dst_dir);
dst_dir = g_strdup("/");
}
req_obj = json_object ();
json_object_set_new (req_obj, "src_repo_id", json_string(repo_id1));
json_object_set_new (req_obj, "src_parent_dir", json_string(src_dir));
array = json_array ();
json_array_append_new (array, json_string(src_filename));
json_object_set_new (req_obj, "src_dirents", array);
json_object_set_new (req_obj, "dst_repo_id", json_string(repo_id2));
json_object_set_new (req_obj, "dst_parent_dir", json_string(dst_dir));
req_content = json_dumps (req_obj, 0);
url = g_strdup_printf ("%s/api/v2.1/repos/async-batch-move-item/", host);
if (http_api_json_post (curl, url, api_token, req_content, strlen(req_content),
&status, &rsp_content, &rsp_size,
TRUE, REPO_OPER_TIMEOUT, NULL) < 0) {
conn->release = TRUE;
ret = -1;
goto out;
}
curl_easy_reset (curl);
if (status != HTTP_OK) {
seaf_warning ("Bad response code for POST %s: %d, response error: %s.\n", url, status, (rsp_content ? rsp_content : "no response body"));
if (status == HTTP_NOT_FOUND && no_api)
*no_api = TRUE;
ret = -1;
goto out;
}
obj = json_loadb (rsp_content, rsp_size, 0, &jerror);
if (!obj) {
seaf_warning ("Failed to load json response: %s\n", jerror.text);
ret = -1;
goto out;
}
if (json_typeof(obj) != JSON_OBJECT) {
seaf_warning ("Response is not a json object.\n");
ret = -1;
goto out;
}
const char *task_id = json_string_value(json_object_get(obj, "task_id"));
if (!task_id || strlen(task_id) == 0) {
if (g_strcmp0 (repo_id1, repo_id2) == 0)
goto out;
seaf_warning ("No copy move task id returned from server.\n");
ret = -1;
goto out;
}
g_free (url);
url = g_strdup_printf ("%s/api/v2.1/query-copy-move-progress/?task_id=%s",
host, task_id);
while (1) {
g_free (rsp_content);
rsp_content = NULL;
if (http_api_get (curl, url, api_token, &status, &rsp_content, &rsp_size,
NULL, NULL, TRUE, REPO_OPER_TIMEOUT, NULL) < 0) {
conn->release = TRUE;
ret = -1;
goto out;
}
curl_easy_reset (curl);
if (status != HTTP_OK) {
seaf_warning ("Bad response code GET %s: %d\n", url, status);
ret = -1;
goto out;
}
json_decref (obj);
obj = json_loadb (rsp_content, rsp_size, 0, &jerror);
if (!obj) {
seaf_warning ("Failed to load json response: %s\n", jerror.text);
ret = -1;
goto out;
}
if (json_typeof(obj) != JSON_OBJECT) {
seaf_warning ("Response is not a json object.\n");
ret = -1;
goto out;
}
json_t *succ = json_object_get (obj, "successful");
if (!succ) {
seaf_warning ("Invalid json object format.\n");
ret = -1;
goto out;
}
json_t *failed = json_object_get (obj, "failed");
if (!failed) {
seaf_warning ("Invalid json object format.\n");
ret = -1;
goto out;
}
if (succ == json_true()) {
break;
} else if (failed == json_true()) {
seaf_warning ("Move %s/%s to %s/%s failed on server.\n",
repo_id1, oldpath, repo_id2, newpath);
ret = -1;
break;
}
seaf_sleep (1);
}
out:
g_free (src_dir);
g_free (src_filename);
g_free (dst_dir);
g_free (url);
g_free (req_content);
g_free (rsp_content);
json_decref (obj);
json_decref (req_obj);
return ret;
}
int
http_tx_manager_api_move_file (HttpTxManager *mgr,
const char *host,
const char *api_token,
const char *repo_id1,
const char *oldpath,
const char *repo_id2,
const char *newpath,
gboolean is_file)
{
HttpTxPriv *priv = seaf->http_tx_mgr->priv;
ConnectionPool *pool;
Connection *conn;
int ret = 0;
pool = find_connection_pool (priv, host);
if (!pool) {
seaf_warning ("Failed to create connection pool for host %s.\n", host);
return -1;
}
conn = connection_pool_get_connection (pool);
if (!conn) {
seaf_warning ("Failed to get connection to host %s.\n", host);
return -1;
}
gboolean no_api = FALSE;
ret = asynchronous_move (conn, host, api_token,
repo_id1, oldpath, repo_id2, newpath,
is_file, &no_api);
if (ret < 0 && no_api) {
/* Async move api not found, try old api. */
ret = synchronous_move (conn, host, api_token,
repo_id1, oldpath, repo_id2, newpath);
}
connection_pool_return_connection (pool, conn);
return ret;
}
static int
parse_space_usage (const char *rsp_content, int rsp_size, gint64 *total, gint64 *used)
{
json_t *object, *member;
json_error_t jerror;
int ret = 0;
object = json_loadb (rsp_content, rsp_size, 0, &jerror);
if (!object) {
seaf_warning ("Failed to load json object: %s\n", jerror.text);
return -1;
}
if (json_typeof(object) != JSON_OBJECT) {
seaf_warning ("Json response is not object\n");
ret = -1;
goto out;
}
member = json_object_get (object, "total");
if (json_typeof(member) != JSON_INTEGER) {
seaf_warning ("Space usage not an integer\n");
ret = -1;
goto out;
}
*total = json_integer_value (member);
member = json_object_get (object, "usage");
if (json_typeof(member) != JSON_INTEGER) {
seaf_warning ("Space usage not an integer\n");
ret = -1;
goto out;
}
*used = json_integer_value (member);
out:
json_decref (object);
return ret;
}
int
http_tx_manager_api_get_space_usage (HttpTxManager *mgr,
const char *host,
const char *api_token,
gint64 *total,
gint64 *used)
{
HttpTxPriv *priv = seaf->http_tx_mgr->priv;
ConnectionPool *pool;
Connection *conn;
CURL *curl;
char *url = NULL;
int status;
char *rsp_content = NULL;
gint64 rsp_size;
int ret = 0;
pool = find_connection_pool (priv, host);
if (!pool) {
seaf_warning ("Failed to create connection pool for host %s.\n", host);
return -1;
}
conn = connection_pool_get_connection (pool);
if (!conn) {
seaf_warning ("Failed to get connection to host %s.\n", host);
return -1;
}
curl = conn->curl;
url = g_strdup_printf ("%s/api2/account/info/", host);
if (http_api_get (curl, url, api_token,
&status, &rsp_content, &rsp_size,
NULL, NULL, TRUE, HTTP_TIMEOUT_SEC, NULL) < 0) {
conn->release = TRUE;
ret = -1;
goto out;
}
if (status != HTTP_OK) {
seaf_warning ("Bad response code for GET %s: %d.\n", url, status);
ret = -1;
goto out;
}
if (parse_space_usage (rsp_content, rsp_size, total, used) < 0) {
ret = -1;
}
out:
connection_pool_return_connection (pool, conn);
g_free (url);
g_free (rsp_content);
return ret;
}
static int
parse_block_map (const char *rsp_content, gint64 rsp_size,
gint64 **pblock_map, int *n_blocks)
{
json_t *array, *element;
json_error_t jerror;
size_t n, i;
gint64 *block_map = NULL;
int ret = 0;
array = json_loadb (rsp_content, rsp_size, 0, &jerror);
if (!array) {
seaf_warning ("Failed to load json: %s\n", jerror.text);
return -1;
}
if (json_typeof (array) != JSON_ARRAY) {
seaf_warning ("Response is not a json array.\n");
ret = -1;
goto out;
}
n = json_array_size (array);
block_map = g_new0 (gint64, n);
for (i = 0; i < n; ++i) {
element = json_array_get (array, i);
if (json_typeof (element) != JSON_INTEGER) {
seaf_warning ("Block map element not an integer.\n");
ret = -1;
goto out;
}
block_map[i] = (gint64)json_integer_value (element);
}
out:
json_decref (array);
if (ret < 0) {
g_free (block_map);
*pblock_map = NULL;
} else {
*pblock_map = block_map;
*n_blocks = (int)n;
}
return ret;
}
int
http_tx_manager_get_file_block_map (HttpTxManager *mgr,
const char *host,
gboolean use_fileserver_port,
const char *token,
const char *repo_id,
const char *file_id,
gint64 **pblock_map,
int *n_blocks)
{
HttpTxPriv *priv = mgr->priv;
ConnectionPool *pool;
Connection *conn;
CURL *curl;
char *url;
int ret = 0;
char *rsp_content = NULL;
gint64 rsp_size;
int status;
pool = find_connection_pool (priv, host);
if (!pool) {
seaf_warning ("Failed to create connection pool for host %s.\n", host);
return -1;
}
conn = connection_pool_get_connection (pool);
if (!conn) {
seaf_warning ("Failed to get connection to host %s.\n", host);
return -1;
}
curl = conn->curl;
if (!use_fileserver_port)
url = g_strdup_printf ("%s/seafhttp/repo/%s/block-map/%s",
host, repo_id, file_id);
else
url = g_strdup_printf ("%s/repo/%s/block-map/%s",
host, repo_id, file_id);
if (http_get (curl, url, token, &status, &rsp_content, &rsp_size,
NULL, NULL, TRUE, HTTP_TIMEOUT_SEC, NULL) < 0) {
conn->release = TRUE;
ret = -1;
goto out;
}
if (status != HTTP_OK) {
seaf_warning ("Bad response code for GET %s: %d.\n", url, status);
ret = -1;
goto out;
}
if (parse_block_map (rsp_content, rsp_size, pblock_map, n_blocks) < 0) {
ret = -1;
goto out;
}
out:
g_free (url);
g_free (rsp_content);
connection_pool_return_connection (pool, conn);
return ret;
}
int
http_tx_manager_get_commit (HttpTxManager *mgr,
const char *host,
gboolean use_fileserver_port,
const char *sync_token,
const char *repo_id,
const char *commit_id,
char **resp,
gint64 *resp_size)
{
HttpTxPriv *priv = mgr->priv;
ConnectionPool *pool;
Connection *conn;
CURL *curl;
char *url = NULL;
int status;
int ret = 0;
pool = find_connection_pool (priv, host);
if (!pool) {
seaf_warning ("Failed to create connection pool for host %s.\n", host);
return -1;
}
conn = connection_pool_get_connection (pool);
if (!conn) {
seaf_warning ("Failed to get connection to host %s.\n", host);
return -1;
}
curl = conn->curl;
if (!use_fileserver_port)
url = g_strdup_printf ("%s/seafhttp/repo/%s/commit/%s",
host, repo_id, commit_id);
else
url = g_strdup_printf ("%s/repo/%s/commit/%s",
host, repo_id, commit_id);
if (http_get (curl, url, sync_token, &status,
resp, resp_size,
NULL, NULL, TRUE, REPO_OPER_TIMEOUT, NULL) < 0) {
conn->release = TRUE;
ret = -1;
goto out;
}
if (status != HTTP_OK) {
seaf_warning ("Bad response code for GET %s: %d.\n", url, status);
ret = -1;
}
out:
g_free (url);
connection_pool_return_connection (pool, conn);
return ret;
}
int
http_tx_manager_api_create_repo (HttpTxManager *mgr,
const char *host,
const char *api_token,
const char *repo_name,
char **resp,
gint64 *resp_size)
{
HttpTxPriv *priv = mgr->priv;
ConnectionPool *pool;
Connection *conn;
CURL *curl;
char *url = NULL;
char *req_content = NULL;
int status;
int ret = 0;
pool = find_connection_pool (priv, host);
if (!pool) {
seaf_warning ("Failed to create connection pool for host %s.\n", host);
return -1;
}
conn = connection_pool_get_connection (pool);
if (!conn) {
seaf_warning ("Failed to get connection to host %s.\n", host);
return -1;
}
curl = conn->curl;
url = g_strdup_printf ("%s/api2/repos/", host);
req_content = g_strdup_printf ("name=%s&desc=%s", repo_name, repo_name);
if (http_api_post (curl, url, api_token, req_content, strlen(req_content),
&status, resp, resp_size,
TRUE, REPO_OPER_TIMEOUT, NULL) < 0) {
conn->release = TRUE;
ret = -1;
goto out;
}
if (status != HTTP_OK) {
seaf_warning ("Bad response code for POST %s: %d.\n", url, status);
ret = -1;
}
out:
connection_pool_return_connection (pool, conn);
g_free (req_content);
g_free (url);
return ret;
}
int
http_tx_manager_api_rename_repo (HttpTxManager *mgr,
const char *host,
const char *api_token,
const char *repo_id,
const char *new_name)
{
HttpTxPriv *priv = mgr->priv;
ConnectionPool *pool;
Connection *conn;
CURL *curl;
char *url = NULL;
char *req_content = NULL;
char *resp = NULL;
gint64 resp_size;
int status;
int ret = 0;
pool = find_connection_pool (priv, host);
if (!pool) {
seaf_warning ("Failed to create connection pool for host %s.\n", host);
return -1;
}
conn = connection_pool_get_connection (pool);
if (!conn) {
seaf_warning ("Failed to get connection to host %s.\n", host);
return -1;
}
curl = conn->curl;
url = g_strdup_printf ("%s/api2/repos/%s/?op=rename", host, repo_id);
req_content = g_strdup_printf ("repo_name=%s", new_name);
if (http_api_post (curl, url, api_token, req_content, strlen(req_content),
&status, &resp, &resp_size,
TRUE, REPO_OPER_TIMEOUT, NULL) < 0) {
conn->release = TRUE;
ret = -1;
goto out;
}
if (status != HTTP_OK) {
seaf_warning ("Bad response code for POST %s: %d(%s).\n", url, status,
resp ? resp : "");
ret = -1;
}
out:
connection_pool_return_connection (pool, conn);
g_free (req_content);
g_free (resp);
g_free (url);
return ret;
}
int
http_tx_manager_api_delete_repo (HttpTxManager *mgr,
const char *host,
const char *api_token,
const char *repo_id)
{
HttpTxPriv *priv = mgr->priv;
ConnectionPool *pool;
Connection *conn;
CURL *curl;
char *url = NULL;
int status;
int ret = 0;
pool = find_connection_pool (priv, host);
if (!pool) {
seaf_warning ("Failed to create connection pool for host %s.\n", host);
return -1;
}
conn = connection_pool_get_connection (pool);
if (!conn) {
seaf_warning ("Failed to get connection to host %s.\n", host);
return -1;
}
curl = conn->curl;
url = g_strdup_printf ("%s/api/v2.1/repos/%s/", host, repo_id);
if (http_api_delete (curl, url, api_token, &status,
TRUE, REPO_OPER_TIMEOUT) < 0) {
conn->release = TRUE;
ret = -1;
goto out;
}
if (status != HTTP_OK) {
seaf_warning ("Bad response code for DELETE %s:%d.\n", url, status);
ret = -1;
}
out:
connection_pool_return_connection (pool, conn);
g_free (url);
return ret;
}
typedef struct FileRangeData {
char *buf;
size_t size;
} FileRangeData;
static size_t
get_file_range_cb (void *contents, size_t size, size_t nmemb, void *userp)
{
FileRangeData *data = userp;
size_t realsize = size * nmemb;
if (data->size < realsize) {
seaf_warning ("Get file range failed. Returned data size larger than buffer.\n");
return data->size;
}
memcpy (data->buf, contents, realsize);
data->buf += realsize;
data->size -= realsize;
return realsize;
}
gssize
http_tx_manager_get_file_range (HttpTxManager *mgr,
const char *host,
const char *api_token,
const char *repo_id,
const char *path,
char *buf,
guint64 offset,
size_t size)
{
HttpTxPriv *priv = mgr->priv;
ConnectionPool *pool;
Connection *conn;
CURL *curl;
char *url = NULL, *file_url;
char *resp = NULL;
gint64 resp_size;
int status;
int ret = 0;
pool = find_connection_pool (priv, host);
if (!pool) {
seaf_warning ("Failed to create connection pool for host %s.\n", host);
return -1;
}
conn = connection_pool_get_connection (pool);
if (!conn) {
seaf_warning ("Failed to get connection to host %s.\n", host);
return -1;
}
curl = conn->curl;
char *esc_path = g_uri_escape_string(path, NULL, FALSE);
url = g_strdup_printf ("%s/api2/repos/%s/file/?p=%s", host, repo_id, esc_path);
g_free (esc_path);
if (http_api_get (curl, url, api_token,
&status, &resp, &resp_size, NULL, NULL,
TRUE, REPO_OPER_TIMEOUT, NULL) < 0) {
conn->release = TRUE;
ret = -1;
goto out;
}
if (status != HTTP_OK) {
seaf_warning ("Bad response code for GET %s: %d(%s).\n", url, status,
resp ? resp : "");
ret = -1;
goto out;
}
/* Returned file url is in "url" format. */
resp[resp_size-1] = '\0';
file_url = resp + 1;
FileRangeData data;
data.buf = buf;
data.size = size;
curl_easy_reset (curl);
if (http_get_range (curl, file_url, NULL,
&status, NULL, NULL, get_file_range_cb, &data,
TRUE, REPO_OPER_TIMEOUT,
offset, offset+size-1) < 0) {
conn->release = TRUE;
ret = -1;
goto out;
}
if (status != HTTP_RES_PARTIAL) {
seaf_warning ("Failed to get file range. Bad response code for GET %s: %d.\n",
url, status);
ret = -1;
goto out;
}
ret = (int)(size - data.size);
out:
connection_pool_return_connection (pool, conn);
g_free (resp);
g_free (url);
return ret;
}
/* typedef struct FileConflictData { */
/* char *repo_id; */
/* char *repo_name; */
/* char *path; */
/* } FileConflictData; */
/* static void */
/* notify_conflict (CEvent *event, void *handler_data) */
/* { */
/* FileConflictData *data = event->data; */
/* json_t *object; */
/* char *str; */
/* object = json_object (); */
/* json_object_set_new (object, "repo_id", json_string(data->repo_id)); */
/* json_object_set_new (object, "repo_name", json_string(data->repo_name)); */
/* json_object_set_new (object, "path", json_string(data->path)); */
/* str = json_dumps (object, 0); */
/* seaf_mq_manager_publish_notification (seaf->mq_mgr, */
/* "sync.conflict", */
/* str); */
/* free (str); */
/* json_decref (object); */
/* g_free (data->repo_id); */
/* g_free (data->repo_name); */
/* g_free (data->path); */
/* g_free (data); */
/* } */
/* void */
/* http_tx_manager_notify_conflict (HttpTxTask *task, const char *path) */
/* { */
/* FileConflictData *data = g_new0 (FileConflictData, 1); */
/* data->repo_id = g_strdup(task->repo_id); */
/* data->repo_name = g_strdup(task->repo_name); */
/* data->path = g_strdup(path); */
/* cevent_manager_add_event (seaf->ev_mgr, task->cevent_id, data); */
/* } */
GList*
http_tx_manager_get_upload_tasks (HttpTxManager *manager)
{
return g_hash_table_get_values (manager->priv->upload_tasks);
}
GList*
http_tx_manager_get_download_tasks (HttpTxManager *manager)
{
return g_hash_table_get_values (manager->priv->download_tasks);
}
HttpTxTask *
http_tx_manager_find_task (HttpTxManager *manager, const char *repo_id)
{
HttpTxTask *task = NULL;
task = g_hash_table_lookup (manager->priv->upload_tasks, repo_id);
if (task)
return task;
task = g_hash_table_lookup (manager->priv->download_tasks, repo_id);
return task;
}
void
http_tx_manager_cancel_task (HttpTxManager *manager,
const char *repo_id,
int task_type)
{
HttpTxTask *task = NULL;
if (task_type == HTTP_TASK_TYPE_DOWNLOAD)
task = g_hash_table_lookup (manager->priv->download_tasks, repo_id);
else
task = g_hash_table_lookup (manager->priv->upload_tasks, repo_id);
if (!task)
return;
if (task->state != HTTP_TASK_STATE_NORMAL) {
return;
}
if (task->runtime_state == HTTP_TASK_RT_STATE_INIT) {
transition_state (task, HTTP_TASK_STATE_CANCELED, HTTP_TASK_RT_STATE_FINISHED);
return;
}
/* Only change state. runtime_state will be changed in worker thread. */
transition_state (task, HTTP_TASK_STATE_CANCELED, task->runtime_state);
}
int
http_tx_task_get_rate (HttpTxTask *task)
{
return task->last_tx_bytes;
}
const char *
http_task_state_to_str (int state)
{
if (state < 0 || state >= N_HTTP_TASK_STATE)
return "unknown";
return http_task_state_str[state];
}
const char *
http_task_rt_state_to_str (int rt_state)
{
if (rt_state < 0 || rt_state >= N_HTTP_TASK_RT_STATE)
return "unknown";
return http_task_rt_state_str[rt_state];
}
const char *
http_task_error_str (int task_errno)
{
if (task_errno < 0 || task_errno >= N_HTTP_TASK_ERROR)
return "unknown error";
return http_task_error_strs[task_errno];
}
static void
collect_uploading_files (gpointer key, gpointer value,
gpointer user_data)
{
char *abs_path = key;
FileUploadProgress *progress = value;
json_t *uploading = user_data;
json_t *upload_info = json_object ();
json_object_set_string_member (upload_info, "server", progress->server);
json_object_set_string_member (upload_info, "username", progress->user);
json_object_set_string_member (upload_info, "file_path", abs_path);
json_object_set_int_member (upload_info, "uploaded", progress->uploaded);
json_object_set_int_member (upload_info, "total_upload", progress->total_upload);
json_array_append_new (uploading, upload_info);
}
json_t *
http_tx_manager_get_upload_progress (HttpTxManager *mgr)
{
HttpTxPriv *priv = mgr->priv;
int i = 0;
FileUploadedInfo *uploaded_info;
json_t *uploaded = json_array ();
json_t *uploading = json_array ();
json_t *progress = json_object ();
json_t *uploaded_obj;
pthread_mutex_lock (&priv->progress_lock);
g_hash_table_foreach (priv->uploading_files, collect_uploading_files,
uploading);
while (i < MAX_GET_FINISHED_FILES) {
uploaded_info = g_queue_peek_nth (priv->uploaded_files, i);
if (uploaded_info) {
uploaded_obj = json_object ();
json_object_set_string_member (uploaded_obj, "server", uploaded_info->server);
json_object_set_string_member (uploaded_obj, "username", uploaded_info->user);
json_object_set_string_member (uploaded_obj, "file_path", uploaded_info->file_path);
json_array_append_new (uploaded, uploaded_obj);
} else {
break;
}
i++;
}
pthread_mutex_unlock (&priv->progress_lock);
json_object_set_new (progress, "uploaded_files", uploaded);
json_object_set_new (progress, "uploading_files", uploading);
return progress;
}
#if 0
#define UPLOADED_FILE_LIST_NAME "uploaded.json"
#define SAVE_UPLOADED_FILE_LIST_INTERVAL 5
#define UPLOADED_FILE_LIST_VERSION 1
static void *
save_uploaded_file_list_worker (void *data)
{
HttpTxManager *mgr = data;
char *path = g_build_filename (seaf->seaf_dir, UPLOADED_FILE_LIST_NAME, NULL);
json_t *progress = NULL, *uploaded, *list;
char *txt = NULL;
GError *error = NULL;
while (1) {
seaf_sleep (SAVE_UPLOADED_FILE_LIST_INTERVAL);
progress = http_tx_manager_get_upload_progress (mgr);
uploaded = json_object_get (progress, "uploaded_files");
if (json_array_size(uploaded) > 0) {
list = json_object();
json_object_set_new (list, "version",
json_integer(UPLOADED_FILE_LIST_VERSION));
json_object_set (list, "uploaded_files", uploaded);
txt = json_dumps (list, 0);
if (!g_file_set_contents (path, txt, -1, &error)) {
seaf_warning ("Failed to save uploaded file list: %s\n",
error->message);
g_clear_error (&error);
}
json_decref (list);
free (txt);
}
json_decref (progress);
}
g_free (path);
return NULL;
}
static void
load_uploaded_file_list (HttpTxManager *mgr)
{
char *path = g_build_filename (seaf->seaf_dir, UPLOADED_FILE_LIST_NAME, NULL);
char *txt = NULL;
gsize len;
GError *error = NULL;
json_t *list = NULL, *uploaded = NULL;
json_error_t jerror;
if (!g_file_test (path, G_FILE_TEST_IS_REGULAR)) {
g_free (path);
return;
}
if (!g_file_get_contents (path, &txt, &len, &error)) {
seaf_warning ("Failed to read uploaded file list: %s\n", error->message);
g_clear_error (&error);
g_free (path);
return;
}
list = json_loadb (txt, len, 0, &jerror);
if (!list) {
seaf_warning ("Failed to load uploaded file list: %s\n", jerror.text);
goto out;
}
if (json_typeof(list) != JSON_OBJECT) {
seaf_warning ("Bad uploaded file list format.\n");
goto out;
}
uploaded = json_object_get (list, "uploaded_files");
if (!uploaded) {
seaf_warning ("No uploaded_files in json object.\n");
goto out;
}
if (json_typeof(uploaded) != JSON_ARRAY) {
seaf_warning ("Bad uploaded file list format.\n");
goto out;
}
json_t *iter;
size_t n = json_array_size (uploaded), i;
for (i = 0; i < n; ++i) {
iter = json_array_get (uploaded, i);
if (json_typeof(iter) != JSON_STRING) {
seaf_warning ("Bad uploaded file list format.\n");
goto out;
}
g_queue_push_tail (mgr->priv->uploaded_files,
g_strdup(json_string_value(iter)));
}
out:
if (list)
json_decref (list);
g_free (path);
g_free (txt);
}
#endif
seadrive-fuse-3.0.13/src/http-tx-mgr.h 0000664 0000000 0000000 00000035252 14761776747 0017534 0 ustar 00root root 0000000 0000000 #ifndef HTTP_TX_MGR_H
#define HTTP_TX_MGR_H
#include
#include
enum {
HTTP_TASK_TYPE_DOWNLOAD = 0,
HTTP_TASK_TYPE_UPLOAD,
};
/**
* The state that can be set by user.
*
* A task in NORMAL state can be canceled;
* A task in RT_STATE_FINISHED can be removed.
*/
enum HttpTaskState {
HTTP_TASK_STATE_NORMAL = 0,
HTTP_TASK_STATE_CANCELED,
HTTP_TASK_STATE_FINISHED,
HTTP_TASK_STATE_ERROR,
N_HTTP_TASK_STATE,
};
enum HttpTaskRuntimeState {
HTTP_TASK_RT_STATE_INIT = 0,
HTTP_TASK_RT_STATE_CHECK,
HTTP_TASK_RT_STATE_COMMIT,
HTTP_TASK_RT_STATE_FS,
HTTP_TASK_RT_STATE_BLOCK, /* Only used in upload. */
HTTP_TASK_RT_STATE_UPDATE_BRANCH, /* Only used in upload. */
HTTP_TASK_RT_STATE_FINISHED,
N_HTTP_TASK_RT_STATE,
};
enum HttpTaskError {
HTTP_TASK_OK = 0,
HTTP_TASK_ERR_FORBIDDEN,
HTTP_TASK_ERR_NO_WRITE_PERMISSION,
HTTP_TASK_ERR_NO_PERMISSION_TO_SYNC,
HTTP_TASK_ERR_NET,
HTTP_TASK_ERR_RESOLVE_PROXY,
HTTP_TASK_ERR_RESOLVE_HOST,
HTTP_TASK_ERR_CONNECT,
HTTP_TASK_ERR_SSL,
HTTP_TASK_ERR_TX,
HTTP_TASK_ERR_TX_TIMEOUT,
HTTP_TASK_ERR_UNHANDLED_REDIRECT,
HTTP_TASK_ERR_SERVER,
HTTP_TASK_ERR_BAD_REQUEST,
HTTP_TASK_ERR_BAD_LOCAL_DATA,
HTTP_TASK_ERR_NOT_ENOUGH_MEMORY,
HTTP_TASK_ERR_WRITE_LOCAL_DATA,
HTTP_TASK_ERR_NO_QUOTA,
HTTP_TASK_ERR_FILES_LOCKED,
HTTP_TASK_ERR_REPO_DELETED,
HTTP_TASK_ERR_REPO_CORRUPTED,
HTTP_TASK_ERR_FILE_LOCKED,
HTTP_TASK_ERR_FOLDER_PERM_DENIED,
HTTP_TASK_ERR_LIBRARY_TOO_LARGE,
HTTP_TASK_ERR_TOO_MANY_FILES,
HTTP_TASK_ERR_BLOCK_MISSING,
HTTP_TASK_ERR_UNKNOWN,
N_HTTP_TASK_ERROR,
};
struct _SeafileSession;
struct _HttpTxPriv;
struct _HttpTxManager {
struct _SeafileSession *seaf;
struct _HttpTxPriv *priv;
};
typedef struct _HttpTxManager HttpTxManager;
struct _HttpTxTask {
HttpTxManager *manager;
char repo_id[37];
int repo_version;
// repo_uname is the same as display_name.
char *repo_uname;
char *token;
int protocol_version;
int type;
char *host;
gboolean is_clone;
char *email;
gboolean use_fileserver_port;
char *server;
char *user;
char head[41];
int state;
int runtime_state;
int error;
/* Used to signify stop transfer for all threads. */
gboolean all_stop;
/* For clone fs object progress */
int n_fs_objs;
int done_fs_objs;
/* For upload progress */
int n_blocks;
int done_blocks;
/* For download progress */
int n_files;
int done_files;
gint tx_bytes; /* bytes transferred in this second. */
gint last_tx_bytes; /* bytes transferred in the last second. */
char *unsyncable_path;
};
typedef struct _HttpTxTask HttpTxTask;
HttpTxManager *
http_tx_manager_new (struct _SeafileSession *seaf);
int
http_tx_manager_start (HttpTxManager *mgr);
int
http_tx_manager_add_download (HttpTxManager *manager,
const char *repo_id,
int repo_version,
const char *server,
const char *user,
const char *host,
const char *token,
const char *server_head_id,
gboolean is_clone,
int protocol_version,
gboolean use_fileserver_port,
GError **error);
int
http_tx_manager_add_upload (HttpTxManager *manager,
const char *repo_id,
int repo_version,
const char *server,
const char *user,
const char *repo_uname,
const char *host,
const char *token,
int protocol_version,
gboolean use_fileserver_port,
GError **error);
struct _HttpProtocolVersion {
gboolean check_success; /* TRUE if we get response from the server. */
gboolean not_supported;
int version;
int error_code;
};
typedef struct _HttpProtocolVersion HttpProtocolVersion;
typedef void (*HttpProtocolVersionCallback) (HttpProtocolVersion *result,
void *user_data);
/* Asynchronous interface for getting protocol version from a server.
* Also used to determine if the server support http sync.
*/
int
http_tx_manager_check_protocol_version (HttpTxManager *manager,
const char *host,
gboolean use_fileserver_port,
HttpProtocolVersionCallback callback,
void *user_data);
typedef void (*HttpNotifServerCallback) (gboolean is_alive,
void *user_data);
int
http_tx_manager_check_notif_server (HttpTxManager *manager,
const char *host,
gboolean use_notif_server_port,
HttpNotifServerCallback callback,
void *user_data);
struct _HttpHeadCommit {
gboolean check_success;
gboolean perm_denied;
gboolean is_corrupt;
gboolean is_deleted;
char head_commit[41];
int error_code;
};
typedef struct _HttpHeadCommit HttpHeadCommit;
typedef void (*HttpHeadCommitCallback) (HttpHeadCommit *result,
void *user_data);
/* Asynchronous interface for getting head commit info from a server. */
int
http_tx_manager_check_head_commit (HttpTxManager *manager,
const char *repo_id,
int repo_version,
const char *host,
const char *token,
gboolean use_fileserver_port,
HttpHeadCommitCallback callback,
void *user_data);
typedef struct _HttpFolderPermReq {
char repo_id[37];
char *token;
gint64 timestamp;
} HttpFolderPermReq;
typedef struct _HttpFolderPermRes {
char repo_id[37];
gint64 timestamp;
GList *user_perms;
GList *group_perms;
} HttpFolderPermRes;
void
http_folder_perm_req_free (HttpFolderPermReq *req);
void
http_folder_perm_res_free (HttpFolderPermRes *res);
struct _HttpFolderPerms {
gboolean success;
GList *results; /* List of HttpFolderPermRes */
};
typedef struct _HttpFolderPerms HttpFolderPerms;
typedef void (*HttpGetFolderPermsCallback) (HttpFolderPerms *result,
void *user_data);
/* Asynchronous interface for getting folder permissions for a repo. */
int
http_tx_manager_get_folder_perms (HttpTxManager *manager,
const char *host,
gboolean use_fileserver_port,
GList *folder_perm_requests, /* HttpFolderPermReq */
HttpGetFolderPermsCallback callback,
void *user_data);
typedef struct _HttpLockedFilesReq {
char repo_id[37];
char *token;
gint64 timestamp;
} HttpLockedFilesReq;
typedef struct _HttpLockedFilesRes {
char repo_id[37];
gint64 timestamp;
GHashTable *locked_files; /* path -> by_me */
} HttpLockedFilesRes;
void
http_locked_files_req_free (HttpLockedFilesReq *req);
void
http_locked_files_res_free (HttpLockedFilesRes *res);
struct _HttpLockedFiles {
gboolean success;
GList *results; /* List of HttpLockedFilesRes */
};
typedef struct _HttpLockedFiles HttpLockedFiles;
typedef void (*HttpGetLockedFilesCallback) (HttpLockedFiles *result,
void *user_data);
/* Asynchronous interface for getting locked files for a repo. */
int
http_tx_manager_get_locked_files (HttpTxManager *manager,
const char *host,
gboolean use_fileserver_port,
GList *locked_files_requests,
HttpGetLockedFilesCallback callback,
void *user_data);
/* Synchronous interface for locking/unlocking a file on the server. */
int
http_tx_manager_lock_file (HttpTxManager *manager,
const char *host,
gboolean use_fileserver_port,
const char *token,
const char *repo_id,
const char *path);
int
http_tx_manager_unlock_file (HttpTxManager *manager,
const char *host,
gboolean use_fileserver_port,
const char *token,
const char *repo_id,
const char *path);
/* Asynchronous interface for sending a API GET request to seahub. */
struct _HttpAPIGetResult {
gboolean success;
char *rsp_content;
int rsp_size;
int error_code;
int http_status;
};
typedef struct _HttpAPIGetResult HttpAPIGetResult;
typedef void (*HttpAPIGetCallback) (HttpAPIGetResult *result,
void *user_data);
int
http_tx_manager_api_get (HttpTxManager *manager,
const char *host,
const char *url,
const char *token,
HttpAPIGetCallback callback,
void *user_data);
int
http_tx_manager_fileserver_api_get (HttpTxManager *manager,
const char *host,
const char *url,
const char *token,
HttpAPIGetCallback callback,
void *user_data);
int
http_tx_task_download_file_blocks (HttpTxTask *task, const char *file_id);
GList*
http_tx_manager_get_upload_tasks (HttpTxManager *manager);
GList*
http_tx_manager_get_download_tasks (HttpTxManager *manager);
HttpTxTask *
http_tx_manager_find_task (HttpTxManager *manager, const char *repo_id);
void
http_tx_manager_cancel_task (HttpTxManager *manager,
const char *repo_id,
int task_type);
/* Only useful for download task. */
void
http_tx_manager_notify_conflict (HttpTxTask *task, const char *path);
int
http_tx_task_get_rate (HttpTxTask *task);
const char *
http_task_state_to_str (int state);
const char *
http_task_rt_state_to_str (int rt_state);
const char *
http_task_error_str (int task_errno);
typedef size_t (*HttpRecvCallback) (void *, size_t, size_t, void *);
int
http_tx_manager_get_block (HttpTxManager *mgr, const char *host,
gboolean use_fileserver_port, const char *token,
const char *repo_id, const char *block_id,
HttpRecvCallback blk_cb, void *user_data,
int *http_status);
int
http_tx_manager_get_fs_object (HttpTxManager *mgr, const char *host,
gboolean use_fileserver_port, const char *token,
const char *repo_id, const char *obj_id, const char *file_path);
int
http_tx_manager_get_file (HttpTxManager *mgr, const char *host,
gboolean use_fileserver_port, const char *token,
const char *repo_id,
const char *path,
gint64 block_offset,
HttpRecvCallback get_blk_cb, void *user_data,
int *http_status);
int
http_tx_manager_api_move_file (HttpTxManager *mgr,
const char *host,
const char *api_token,
const char *repo_id1,
const char *oldpath,
const char *repo_id2,
const char *newpath,
gboolean is_file);
int
http_tx_manager_get_file_block_map (HttpTxManager *mgr,
const char *host,
gboolean use_fileserver_port,
const char *token,
const char *repo_id,
const char *file_id,
gint64 **block_map,
int *n_blocks);
int
http_tx_manager_api_get_space_usage (HttpTxManager *mgr,
const char *host,
const char *api_token,
gint64 *total,
gint64 *used);
int
http_tx_manager_get_commit (HttpTxManager *mgr,
const char *host,
gboolean use_fileserver_port,
const char *sync_token,
const char *repo_id,
const char *commit_id,
char **resp,
gint64 *resp_size);
int
http_tx_manager_api_create_repo (HttpTxManager *mgr,
const char *host,
const char *api_token,
const char *repo_name,
char **resp,
gint64 *resp_size);
int
http_tx_manager_api_rename_repo (HttpTxManager *mgr,
const char *host,
const char *api_token,
const char *repo_id,
const char *new_name);
int
http_tx_manager_api_delete_repo (HttpTxManager *mgr,
const char *host,
const char *api_token,
const char *repo_id);
gssize
http_tx_manager_get_file_range (HttpTxManager *mgr,
const char *host,
const char *api_token,
const char *repo_id,
const char *path,
char *buf,
guint64 offset,
size_t size);
// "uploading_files": [{"file_path":, "uploaded":, "total_upload":}, ], "uploaded_files": [ten latest uploaded files]}
json_t *
http_tx_manager_get_upload_progress (HttpTxManager *mgr);
#endif
seadrive-fuse-3.0.13/src/job-mgr.c 0000664 0000000 0000000 00000006344 14761776747 0016671 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#include "common.h"
#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
#include
#include
#else
#include
#endif
#include
#include
#include
#include
#include
#include "utils.h"
#include "log.h"
#include "seafile-session.h"
#include "job-mgr.h"
struct _SeafJobManager {
SeafileSession *session;
GThreadPool *thread_pool;
int next_job_id;
};
struct _SeafJob {
SeafJobManager *manager;
int id;
seaf_pipe_t pipefd[2];
JobThreadFunc thread_func;
JobDoneCallback done_func; /* called when the thread is done */
void *data;
/* the done callback should only access this field */
void *result;
};
typedef struct _SeafJob SeafJob;
SeafJob *
seaf_job_new ()
{
SeafJob *job;
job = g_new0 (SeafJob, 1);
return job;
}
void
seaf_job_free (SeafJob *job)
{
g_free (job);
}
static void
job_thread_wrapper (void *vdata, void *unused)
{
SeafJob *job = vdata;
job->result = job->thread_func (job->data);
if (seaf_pipe_writen (job->pipefd[1], "a", 1) != 1) {
seaf_warning ("[Job Manager] write to pipe error: %s\n", strerror(errno));
}
}
static void
job_done_cb (evutil_socket_t fd, short event, void *vdata)
{
SeafJob *job = vdata;
char buf[1];
if (seaf_pipe_readn (job->pipefd[0], buf, 1) != 1) {
seaf_warning ("[Job Manager] read pipe error: %s\n", strerror(errno));
}
seaf_pipe_close (job->pipefd[0]);
seaf_pipe_close (job->pipefd[1]);
if (job->done_func) {
job->done_func (job->result);
}
seaf_job_free (job);
}
int
job_thread_create (SeafJob *job)
{
SeafileSession *session = job->manager->session;
if (seaf_pipe (job->pipefd) < 0) {
seaf_warning ("[Job Manager] pipe error: %s\n", strerror(errno));
return -1;
}
g_thread_pool_push (job->manager->thread_pool, job, NULL);
event_base_once (session->ev_base, job->pipefd[0], EV_READ, job_done_cb, job, NULL);
return 0;
}
SeafJobManager *
seaf_job_manager_new (SeafileSession *session, int max_threads)
{
SeafJobManager *mgr;
mgr = g_new0 (SeafJobManager, 1);
mgr->session = session;
mgr->thread_pool = g_thread_pool_new (job_thread_wrapper,
NULL,
max_threads,
FALSE,
NULL);
return mgr;
}
void
seaf_job_manager_free (SeafJobManager *mgr)
{
g_thread_pool_free (mgr->thread_pool, TRUE, FALSE);
g_free (mgr);
}
int
seaf_job_manager_schedule_job (SeafJobManager *mgr,
JobThreadFunc func,
JobDoneCallback done_func,
void *data)
{
SeafJob *job = seaf_job_new ();
job->id = mgr->next_job_id++;
job->manager = mgr;
job->thread_func = func;
job->done_func = done_func;
job->data = data;
if (job_thread_create (job) < 0) {
seaf_job_free (job);
return -1;
}
return 0;
}
seadrive-fuse-3.0.13/src/job-mgr.h 0000664 0000000 0000000 00000001624 14761776747 0016672 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/**
* Job Manager manages long term jobs. These jobs are run in their
* own threads.
*/
#ifndef SEAF_JOB_MGR_H
#define SEAF_JOB_MGR_H
struct _SeafJobManager;
typedef struct _SeafJobManager SeafJobManager;
struct _SeafileSession;
/*
The thread func should return the result back by
return (void *)result;
The result will be passed to JobDoneCallback.
*/
typedef void* (*JobThreadFunc)(void *data);
typedef void (*JobDoneCallback)(void *result);
SeafJobManager *
seaf_job_manager_new (struct _SeafileSession *session, int max_threads);
void
seaf_job_manager_free (struct _SeafJobManager *mgr);
int
seaf_job_manager_schedule_job (struct _SeafJobManager *mgr,
JobThreadFunc func,
JobDoneCallback done_func,
void *data);
#endif
seadrive-fuse-3.0.13/src/journal-mgr.c 0000664 0000000 0000000 00000036755 14761776747 0017602 0 ustar 00root root 0000000 0000000 #include "common.h"
#include
#include "log.h"
#include "db.h"
#include "utils.h"
#include "seafile-session.h"
#include "journal-mgr.h"
struct _Journal {
char *repo_id;
/* The journal associates a worker thread that continuously writes
* op entries into the database. It reads from @op_queue and commits a
* transaction to the database in two cases:
* 1. 100 op entries have been received.
* 2. no new op entry is received in 1 second.
*/
GAsyncQueue *op_queue;
gint pushed_ops;
gint committed_ops;
/* The underlying database for the journal.
* To reduce the write latency, ops are inserted into sqlite in
* 100-entry transactions.
*/
char *jdb_path;
sqlite3 *jdb;
pthread_mutex_t jdb_lock;
gboolean write_failed;
char *error_message;
/* Statistics used for generating commit. */
/* Time of last operation written to db */
gint last_change_time;
/* Time of last commit generation */
gint last_commit_time;
/* Total size of files waiting for commit. */
gint64 total_size;
};
struct _JournalManager {
SeafileSession *session;
char *journal_dir;
};
JournalManager *
journal_manager_new (struct _SeafileSession *session)
{
JournalManager *manager = g_new0 (JournalManager, 1);
manager->session = session;
char *journal_dir = g_build_filename (session->seaf_dir, "journals", NULL);
if (g_mkdir_with_parents (journal_dir, 0777) < 0) {
seaf_warning ("Journal dir doesn't exist and failed to create it.\n");
g_free (manager);
g_free (journal_dir);
return NULL;
}
manager->journal_dir = journal_dir;
return manager;
}
int
journal_manager_start (JournalManager *mgr)
{
return 0;
}
JournalOp *
journal_op_new (JournalOpType type, const char *path, const char *new_path,
gint64 size, gint64 mtime, guint32 mode)
{
JournalOp *op = g_new0 (JournalOp, 1);
op->type = type;
op->path = g_strdup(path);
op->new_path = g_strdup(new_path);
op->size = size;
op->mtime = mtime;
op->mode = mode;
return op;
}
void
journal_op_free (JournalOp *op)
{
if (!op)
return;
g_free (op->path);
g_free (op->new_path);
g_free (op);
}
static gboolean
journal_op_same (JournalOp *op1, JournalOp *op2)
{
return ((op1->type == op2->type) &&
(g_strcmp0(op1->path, op2->path) == 0) &&
(g_strcmp0(op1->new_path, op2->new_path) == 0));
}
JournalOp *
journal_op_from_json (const char *json_str)
{
json_t *obj;
json_error_t error;
int type;
const char *path = NULL, *new_path = NULL;
gint64 size = 0, mtime = 0;
guint32 mode = 0;
JournalOp *op = NULL;
obj = json_loads (json_str, 0, &error);
if (!obj) {
seaf_warning ("Invalid json format for operation: %s.\n"
"JSON string: %s\n", error.text, json_str);
return NULL;
}
if (!json_object_has_member (obj, "type")) {
seaf_warning ("Invalid operation format: no type.\n");
goto out;
}
type = (int)json_object_get_int_member (obj, "type");
if (!json_object_has_member (obj, "path")) {
seaf_warning ("Invalid operation format: no path.\n");
goto out;
}
path = json_object_get_string_member (obj, "path");
if (!path) {
seaf_warning ("Invalid operation format: null path.\n");
goto out;
}
if (type == OP_TYPE_RENAME) {
new_path = json_object_get_string_member (obj, "new_path");
if (!new_path) {
seaf_warning ("Invalid operation format: no new_path.\n");
goto out;
}
} else if (type == OP_TYPE_CREATE_FILE || type == OP_TYPE_UPDATE_FILE ||
type == OP_TYPE_UPDATE_ATTR) {
if (!json_object_has_member (obj, "size")) {
seaf_warning ("Invalid operation format: no size.\n");
goto out;
}
size = json_object_get_int_member (obj, "size");
if (!json_object_has_member (obj, "mtime")) {
seaf_warning ("Invalid operation format: no mtime.\n");
goto out;
}
mtime = json_object_get_int_member (obj, "mtime");
if (!json_object_has_member (obj, "mode")) {
seaf_warning ("Invalid operation format: no mode.\n");
goto out;
}
mode = json_object_get_int_member (obj, "mode");
}
op = journal_op_new (type, path, new_path, size, mtime, mode);
out:
json_decref (obj);
return op;
}
char *
journal_op_to_json (JournalOp *op)
{
json_t *obj;
char *json_str = NULL;
obj = json_object ();
json_object_set_int_member (obj, "type", (int)op->type);
json_object_set_string_member (obj, "path", op->path);
if (op->type == OP_TYPE_RENAME) {
json_object_set_string_member (obj, "new_path", op->new_path);
} else if (op->type == OP_TYPE_CREATE_FILE || op->type == OP_TYPE_UPDATE_FILE ||
op->type == OP_TYPE_UPDATE_ATTR) {
json_object_set_int_member (obj, "size", op->size);
json_object_set_int_member (obj, "mtime", op->mtime);
json_object_set_int_member (obj, "mode", op->mode);
}
json_str = json_dumps (obj, JSON_COMPACT);
if (!json_str) {
seaf_warning ("Failed to dump json for operation.\n");
}
json_decref (obj);
return json_str;
}
static void *write_journal_worker (void *vdata);
static void
journal_free (Journal *journal)
{
if (!journal)
return;
g_free (journal->repo_id);
g_async_queue_unref (journal->op_queue);
g_free (journal->jdb_path);
if (journal->jdb)
sqlite_close_db (journal->jdb);
pthread_mutex_destroy (&journal->jdb_lock);
g_free (journal);
}
static int
do_open_journal (JournalManager *mgr, Journal *journal)
{
sqlite3 *db = NULL;
char *sql;
if (sqlite_open_db (journal->jdb_path, &db) < 0) {
return -1;
}
sql = "CREATE TABLE IF NOT EXISTS Operations ("
"opid INTEGER PRIMARY KEY AUTOINCREMENT, operation TEXT)";
if (sqlite_query_exec (db, sql) < 0) {
sqlite_close_db (db);
return -1;
}
pthread_t tid;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
if (pthread_create (&tid, &attr, write_journal_worker, journal) < 0) {
seaf_warning ("Failed to start write journal worker.\n");
sqlite_close_db (db);
return -1;
}
journal->jdb = db;
return 0;
}
Journal *
journal_manager_open_journal (JournalManager *mgr, const char *repo_id)
{
char *jdb_path = NULL;
Journal *jnl = NULL;
jdb_path = g_strdup_printf ("%s/%s.journal", mgr->journal_dir, repo_id);
jnl = g_new0 (Journal, 1);
jnl->repo_id = g_strdup(repo_id);
jnl->op_queue = g_async_queue_new_full ((GDestroyNotify)journal_op_free);
jnl->jdb_path = jdb_path;
pthread_mutex_init (&jnl->jdb_lock, NULL);
/* Delay creating jdb and worker thread to the time when an op is appended. */
if (seaf_util_exists (jdb_path)) {
if (do_open_journal (mgr, jnl) < 0) {
journal_free (jnl);
return NULL;
}
}
return jnl;
}
int
journal_manager_delete_journal (JournalManager *mgr, const char *repo_id)
{
char *jdb_path = NULL;
int ret;
int n = 0;
jdb_path = g_strdup_printf ("%s/%s.journal", mgr->journal_dir, repo_id);
do {
if (n > 0)
g_usleep (1000000);
ret = seaf_util_unlink (jdb_path);
n++;
if (ret < 0 && n == 10)
seaf_warning ("Failed to delete journal for repo %s after %d retries "
"due to permission issues. Continue retrying.\n",
repo_id, n);
} while (ret < 0 && errno == EACCES);
g_free (jdb_path);
return ret;
}
void
journal_flush (Journal *journal, int timeout)
{
gint pushed, committed;
int elapsed = 0;
while (1) {
pushed = g_atomic_int_get (&journal->pushed_ops);
committed = g_atomic_int_get (&journal->committed_ops);
if (pushed == committed) {
break;
} else if (timeout < 0 || elapsed < timeout) {
g_usleep (G_USEC_PER_SEC);
elapsed++;
} else {
break;
}
}
}
int
journal_close (Journal *journal, gboolean wait)
{
if (!journal->jdb) {
journal_free (journal);
return 0;
}
if (wait) {
journal_flush (journal, -1);
}
JournalOp *op = journal_op_new (OP_TYPE_TERMINATE, NULL, NULL, 0, 0, 0);
g_async_queue_push (journal->op_queue, op);
/* journal will be free in the worker thread. Otherwise the worker thread
* may dereference the freed journal structure.
*/
return 0;
}
static int
insert_op (Journal *journal, JournalOp *op, sqlite3_stmt *stmt)
{
char *op_json;
int ret = 0;
op_json = journal_op_to_json (op);
if (sqlite3_bind_text (stmt, 1, op_json, -1, SQLITE_TRANSIENT) != SQLITE_OK) {
journal->write_failed = TRUE;
journal->error_message = g_strdup(sqlite3_errmsg(journal->jdb));
seaf_warning ("sqlite3_bind_text failed: %s\n", journal->error_message);
ret = -1;
goto out;
}
if (sqlite3_step (stmt) != SQLITE_DONE) {
journal->write_failed = TRUE;
journal->error_message = g_strdup(sqlite3_errmsg(journal->jdb));
seaf_warning ("sqlite3_step failed: %s\n", journal->error_message);
ret = -1;
goto out;
}
out:
sqlite3_clear_bindings (stmt);
sqlite3_reset (stmt);
g_free (op_json);
return ret;
}
static void
flush_write_queue (Journal *journal, GQueue *queue)
{
JournalOp *op;
gint committed = 0;
sqlite3 *jdb = journal->jdb;
char *sql;
sqlite3_stmt *stmt = NULL;
if (g_queue_get_length (queue) == 0)
return;
pthread_mutex_lock (&journal->jdb_lock);
sqlite_begin_transaction (jdb);
sql = "INSERT INTO Operations (operation) VALUES (?)";
if (sqlite3_prepare_v2 (jdb, sql, -1, &stmt, NULL) != SQLITE_OK) {
journal->write_failed = TRUE;
journal->error_message = g_strdup(sqlite3_errmsg(jdb));
sqlite_rollback_transaction (jdb);
goto out;
}
while ((op = (JournalOp *)g_queue_pop_head(queue)) != NULL) {
if (insert_op (journal, op, stmt) < 0) {
/* Put the failed op back to the queue for retry in the next second.
* But previously inserted ops should still be committed.
*/
g_queue_push_head (queue, op);
break;
}
journal_op_free (op);
++committed;
}
if (sqlite_end_transaction (jdb) < 0) {
journal->write_failed = TRUE;
journal->error_message = g_strdup(sqlite3_errmsg(jdb));
sqlite_rollback_transaction (jdb);
goto out;
}
if (journal->write_failed) {
journal->write_failed = FALSE;
g_free (journal->error_message);
journal->error_message = NULL;
}
g_atomic_int_add (&journal->committed_ops, committed);
g_atomic_int_set (&journal->last_change_time, (gint)time(NULL));
out:
pthread_mutex_unlock (&journal->jdb_lock);
if (stmt)
sqlite3_finalize (stmt);
}
#define FLUSH_QUEUE_TIMEOUT G_USEC_PER_SEC
#define WRITE_BATCH 100
static void *
write_journal_worker (void *vdata)
{
Journal *journal = (Journal *)vdata;
JournalOp *op, *prev_op;
GQueue *write_queue = g_queue_new ();
while (1) {
op = (JournalOp *)g_async_queue_timeout_pop (journal->op_queue,
FLUSH_QUEUE_TIMEOUT);
if (!op) {
/* Time out. No op came in in the last second.
* Flush the write queue to db.
*/
flush_write_queue (journal, write_queue);
continue;
}
if (op->type == OP_TYPE_TERMINATE) {
g_queue_free_full (write_queue, (GDestroyNotify)journal_op_free);
journal_free (journal);
break;
}
prev_op = g_queue_peek_tail (write_queue);
if (prev_op && journal_op_same (op, prev_op)) {
if (op->type == OP_TYPE_UPDATE_FILE ||
op->type == OP_TYPE_UPDATE_ATTR) {
prev_op->size = op->size;
prev_op->mtime = op->mtime;
}
journal_op_free (op);
g_atomic_int_inc (&journal->committed_ops);
continue;
} else {
g_queue_push_tail (write_queue, op);
}
if (g_queue_get_length (write_queue) >= WRITE_BATCH ||
((gint)time(NULL) - journal->last_change_time >= FLUSH_QUEUE_TIMEOUT)) {
flush_write_queue (journal, write_queue);
}
}
return NULL;
}
int
journal_append_op (Journal *journal, JournalOp *op)
{
if (!seaf_util_exists (journal->jdb_path)) {
if (do_open_journal (seaf->journal_mgr, journal) < 0)
return -1;
}
g_async_queue_push (journal->op_queue, op);
g_atomic_int_inc (&journal->pushed_ops);
return 0;
}
static gboolean
collect_ops (sqlite3_stmt *stmt, void *vdata)
{
GList **pret = vdata;
gint64 opid;
const char *op_json;
JournalOp *op;
opid = (gint64) sqlite3_column_int64 (stmt, 0);
op_json = (const char *) sqlite3_column_text (stmt, 1);
op = journal_op_from_json (op_json);
if (!op)
return TRUE;
op->opid = opid;
*pret = g_list_prepend (*pret, op);
return TRUE;
}
GList *
journal_read_ops (Journal *journal, gint64 start, gint64 end, gboolean *error)
{
char *sql;
GList *ret = NULL;
if (error)
*error = FALSE;
if (!journal->jdb)
return NULL;
if (start < 0 || end < 0) {
seaf_warning ("BUG: start or end out of range.\n");
return NULL;
}
if (end != G_MAXINT64) {
sql = sqlite3_mprintf ("SELECT opid, operation FROM Operations "
"WHERE opid >= %lld AND "
"opid <= %lld ORDER BY opid",
start, end);
} else {
sql = sqlite3_mprintf ("SELECT opid, operation FROM Operations "
"WHERE opid >= %lld ORDER BY opid",
start, end);
}
pthread_mutex_lock (&journal->jdb_lock);
if (sqlite_foreach_selected_row (journal->jdb, sql, collect_ops, &ret) < 0) {
seaf_warning ("Failed to read ops from journal for repo %s.\n",
journal->repo_id);
g_list_free_full (ret, (GDestroyNotify)journal_op_free);
ret = NULL;
if (error)
*error = TRUE;
goto out;
}
ret = g_list_reverse (ret);
out:
pthread_mutex_unlock (&journal->jdb_lock);
sqlite3_free (sql);
return ret;
}
int
journal_truncate (Journal *journal, gint64 opid)
{
char *sql;
int ret = 0;
if (!journal->jdb)
return 0;
sql = sqlite3_mprintf ("DELETE FROM Operations WHERE opid <= %lld",
opid);
pthread_mutex_lock (&journal->jdb_lock);
if (sqlite_query_exec (journal->jdb, sql) < 0) {
seaf_warning ("Failed to truncate journal for repo %s.\n", journal->repo_id);
ret = -1;
}
pthread_mutex_unlock (&journal->jdb_lock);
sqlite3_free (sql);
return ret;
}
void
journal_get_stat (Journal *journal, JournalStat *st)
{
st->last_change_time = g_atomic_int_get (&journal->last_change_time);
st->last_commit_time = journal->last_commit_time;
st->total_size = journal->total_size;
}
void
journal_set_last_commit_time (Journal *journal, gint time)
{
journal->last_commit_time = time;
}
seadrive-fuse-3.0.13/src/journal-mgr.h 0000664 0000000 0000000 00000004770 14761776747 0017577 0 ustar 00root root 0000000 0000000 #ifndef JOURNAL_MGR_H
#define JOURNAL_MGR_H
#include
struct _SeafileSession;
struct _JournalManager;
typedef struct _JournalManager JournalManager;
JournalManager *
journal_manager_new (struct _SeafileSession *session);
int
journal_manager_start (JournalManager *mgr);
struct _Journal;
typedef struct _Journal Journal;
Journal *
journal_manager_open_journal (JournalManager *mgr, const char *repo_id);
int
journal_manager_delete_journal (JournalManager *mgr, const char *repo_id);
/* New types should only be added to the end of this declaration.
* The type numbers are stored directly to db. Inserting new types in the middle
* will break compatibility.
*/
enum _JournalOpType {
OP_TYPE_CREATE_FILE = 0,
OP_TYPE_DELETE_FILE,
OP_TYPE_UPDATE_FILE,
OP_TYPE_RENAME,
OP_TYPE_MKDIR,
OP_TYPE_RMDIR,
OP_TYPE_UPDATE_ATTR,
OP_TYPE_TERMINATE, /* Special op for journal's internal use. */
};
typedef enum _JournalOpType JournalOpType;
struct _JournalOp {
gint64 opid; /* Only set when the op is read from journal */
JournalOpType type;
char *path;
char *new_path;
gint64 size;
gint64 mtime;
guint32 mode;
};
typedef struct _JournalOp JournalOp;
JournalOp *
journal_op_new (JournalOpType type, const char *path, const char *new_path,
gint64 size, gint64 mtime, guint32 mode);
void
journal_op_free (JournalOp *op);
/* This function takes the ownership of @op. */
int
journal_append_op (Journal *journal, JournalOp *op);
/* Returns the list of operations with opid from @start to @end, inclusively.
* The returned list is sorted by opid, from small to large.
* To get operations from the every beginning, set @start to 0;
* To get operations up to the last one, set @end to G_MAXINT64.
*/
GList *
journal_read_ops (Journal *journal, gint64 start, gint64 end, gboolean *error);
/*
* Truncate journal entries before @opid, inclusive.
*/
int
journal_truncate (Journal *journal, gint64 opid);
/* Wait until all journal ops are flush to disk, with timeout.
* Wait indefinitely if timeout < 0.
*/
void
journal_flush (Journal *journal, int timeout);
/* @wait: wait until all ops are committed to disk. */
int
journal_close (Journal *journal, gboolean wait);
struct _JournalStat {
gint last_change_time;
gint last_commit_time;
gint64 total_size;
};
typedef struct _JournalStat JournalStat;
void
journal_get_stat (Journal *journal, JournalStat *st);
void
journal_set_last_commit_time (Journal *journal, gint time);
#endif
seadrive-fuse-3.0.13/src/log.c 0000664 0000000 0000000 00000006123 14761776747 0016110 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#include "common.h"
#include
#include
#include
#include "log.h"
#include "utils.h"
/* message with greater log levels will be ignored */
static int seafile_log_level;
static char *logfile;
static FILE *logfp;
static void
seafile_log (const gchar *log_domain, GLogLevelFlags log_level,
const gchar *message, gpointer user_data)
{
time_t t;
struct tm *tm;
char buf[1024];
int len;
if (log_level > seafile_log_level)
return;
t = time(NULL);
tm = localtime(&t);
len = strftime (buf, 1024, "[%x %X] ", tm);
g_return_if_fail (len < 1024);
if (logfp != NULL) {
fputs (buf, logfp);
fputs (message, logfp);
fflush (logfp);
} else { // log file not available
printf("%s %s", buf, message);
}
}
int
seafile_log_init (const char *_logfile)
{
g_log_set_handler (NULL, G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL
| G_LOG_FLAG_RECURSION, seafile_log, NULL);
/* record all log message */
seafile_log_level = G_LOG_LEVEL_DEBUG;
if (strcmp(_logfile, "-") == 0) {
logfp = stdout;
logfile = g_strdup (_logfile);
}
else {
logfile = ccnet_expand_path(_logfile);
if ((logfp = g_fopen (logfile, "a+")) == NULL) {
seaf_message ("Failed to open file %s\n", logfile);
return -1;
}
}
return 0;
}
int
seafile_log_reopen ()
{
FILE *fp, *oldfp;
if (strcmp(logfile, "-") == 0)
return 0;
if ((fp = g_fopen (logfile, "a+")) == NULL) {
seaf_message ("Failed to open file %s\n", logfile);
return -1;
}
//TODO: check file's health
oldfp = logfp;
logfp = fp;
if (fclose(oldfp) < 0) {
seaf_message ("Failed to close file %s\n", logfile);
return -1;
}
return 0;
}
static SeafileDebugFlags debug_flags = 0;
static GDebugKey debug_keys[] = {
{ "Transfer", SEAFILE_DEBUG_TRANSFER },
{ "Sync", SEAFILE_DEBUG_SYNC },
{ "Watch", SEAFILE_DEBUG_WATCH },
{ "Http", SEAFILE_DEBUG_HTTP },
{ "Merge", SEAFILE_DEBUG_MERGE },
{ "Fs", SEAFILE_DEBUG_FS },
{ "Curl", SEAFILE_DEBUG_CURL },
{ "Notification", SEAFILE_DEBUG_NOTIFICATION },
{ "Other", SEAFILE_DEBUG_OTHER },
};
gboolean
seafile_debug_flag_is_set (SeafileDebugFlags flag)
{
return (debug_flags & flag) != 0;
}
void
seafile_debug_set_flags (SeafileDebugFlags flags)
{
g_message ("Set debug flags %#x\n", flags);
debug_flags |= flags;
}
void
seafile_debug_set_flags_string (const gchar *flags_string)
{
guint nkeys = G_N_ELEMENTS (debug_keys);
if (flags_string)
seafile_debug_set_flags (
g_parse_debug_string (flags_string, debug_keys, nkeys));
}
void
seafile_debug_impl (SeafileDebugFlags flag, const gchar *format, ...)
{
if (flag & debug_flags) {
va_list args;
va_start (args, format);
g_logv (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, format, args);
va_end (args);
}
}
FILE *
seafile_get_logfp ()
{
return logfp;
}
seadrive-fuse-3.0.13/src/log.h 0000664 0000000 0000000 00000002374 14761776747 0016121 0 ustar 00root root 0000000 0000000 #ifndef LOG_H
#define LOG_H
#include
#define SEAFILE_DOMAIN g_quark_from_string("seafile")
#ifndef seaf_warning
#define seaf_warning(fmt, ...) g_warning("%s(%d): " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#endif
#ifndef seaf_message
#define seaf_message(fmt, ...) g_message("%s(%d): " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#endif
int seafile_log_init (const char *logfile);
int seafile_log_reopen ();
void
seafile_debug_set_flags_string (const gchar *flags_string);
typedef enum
{
SEAFILE_DEBUG_TRANSFER = 1 << 1,
SEAFILE_DEBUG_SYNC = 1 << 2,
SEAFILE_DEBUG_WATCH = 1 << 3, /* wt-monitor */
SEAFILE_DEBUG_HTTP = 1 << 4, /* http server */
SEAFILE_DEBUG_MERGE = 1 << 5,
SEAFILE_DEBUG_FS = 1 << 6, /* Virtual FS */
SEAFILE_DEBUG_CURL = 1 << 7, /* libcurl verbose output */
SEAFILE_DEBUG_NOTIFICATION = 1 << 8,
SEAFILE_DEBUG_OTHER = 1 << 9,
} SeafileDebugFlags;
gboolean
seafile_debug_flag_is_set (SeafileDebugFlags flag);
void seafile_debug_impl (SeafileDebugFlags flag, const gchar *format, ...);
#ifdef DEBUG_FLAG
#undef seaf_debug
#define seaf_debug(fmt, ...) \
seafile_debug_impl (DEBUG_FLAG, "%.10s(%d): " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#endif /* DEBUG_FLAG */
#endif
FILE *seafile_get_logfp ();
seadrive-fuse-3.0.13/src/mq-mgr.c 0000664 0000000 0000000 00000003017 14761776747 0016526 0 ustar 00root root 0000000 0000000 #include "common.h"
#include "log.h"
#include "utils.h"
#include "mq-mgr.h"
typedef struct MqMgrPriv {
// chan <-> async_queue
GHashTable *chans;
} MqMgrPriv;
MqMgr *
mq_mgr_new ()
{
MqMgr *mgr = g_new0 (MqMgr, 1);
mgr->priv = g_new0 (MqMgrPriv, 1);
mgr->priv->chans = g_hash_table_new_full (g_str_hash, g_str_equal,
(GDestroyNotify)g_free,
(GDestroyNotify)g_async_queue_unref);
return mgr;
}
void
mq_mgr_init (MqMgr *mgr)
{
g_hash_table_replace (mgr->priv->chans, g_strdup (SEADRIVE_NOTIFY_CHAN),
g_async_queue_new_full ((GDestroyNotify)json_decref));
g_hash_table_replace (mgr->priv->chans, g_strdup (SEADRIVE_EVENT_CHAN),
g_async_queue_new_full ((GDestroyNotify)json_decref));
}
void
mq_mgr_push_msg (MqMgr *mgr, const char *chan, json_t *msg)
{
GAsyncQueue *async_queue = g_hash_table_lookup (mgr->priv->chans, chan);
if (!async_queue) {
seaf_warning ("Unkonwn message channel %s.\n", chan);
return;
}
if (!msg) {
seaf_warning ("Msg should not be NULL.\n");
return;
}
g_async_queue_push (async_queue, msg);
}
json_t *
mq_mgr_pop_msg (MqMgr *mgr, const char *chan)
{
GAsyncQueue *async_queue = g_hash_table_lookup (mgr->priv->chans, chan);
if (!async_queue) {
seaf_warning ("Unkonwn message channel %s.\n", chan);
return NULL;
}
return g_async_queue_try_pop (async_queue);
}
seadrive-fuse-3.0.13/src/mq-mgr.h 0000664 0000000 0000000 00000000621 14761776747 0016531 0 ustar 00root root 0000000 0000000 #ifndef MQ_MGR_H
#define MQ_MGR_H
#define SEADRIVE_NOTIFY_CHAN "seadrive.notification"
#define SEADRIVE_EVENT_CHAN "seadrive.event"
struct MqMgrPriv;
typedef struct MqMgr {
struct MqMgrPriv *priv;
} MqMgr;
MqMgr *
mq_mgr_new ();
void
mq_mgr_init (MqMgr *mgr);
void
mq_mgr_push_msg (MqMgr *mgr, const char *chan, json_t *msg);
json_t *
mq_mgr_pop_msg (MqMgr *mgr, const char *chan);
#endif
seadrive-fuse-3.0.13/src/notif-mgr.c 0000664 0000000 0000000 00000067052 14761776747 0017241 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#include "common.h"
#include
#include
#include
#include "seafile-session.h"
#include "notif-mgr.h"
#include "sync-mgr.h"
#define DEBUG_FLAG SEAFILE_DEBUG_NOTIFICATION
#include "log.h"
#define NOTIF_PORT 8083
#define RECONNECT_INTERVAL 60 /* 60s */
#define STATUS_DISCONNECTED 0
#define STATUS_CONNECTED 1
#define STATUS_ERROR 2
#define STATUS_CANCELLED 3
typedef struct NotifServer {
struct lws_context *context;
struct lws_client_connect_info i;
struct lws *wsi;
// status of the notification server.
int status;
// whether to close the connection to the server.
gboolean close;
GHashTable *subscriptions;
pthread_mutex_t sub_lock;
GAsyncQueue *messages;
gboolean use_ssl;
char *server_url;
char *addr;
char *path;
int port;
gint refcnt;
} NotifServer;
struct _SeafNotifManagerPriv {
pthread_mutex_t server_lock;
GHashTable *servers;
};
// The Message structure is used to send messages to the server.
typedef struct Message {
void *payload;
size_t len;
int type;
} Message;
static Message*
notif_message_new (const char *str, int type)
{
int len, n;
len = strlen(str) + 1;
Message *msg = g_new0 (Message, 1);
msg->payload = malloc((unsigned int)(LWS_PRE + len));
if (!msg->payload) {
g_free (msg);
return NULL;
}
// The libwebsockets library requires the message to be sent with a LWS_PRE header.
n = lws_snprintf((char *)msg->payload + LWS_PRE, (unsigned int)len, "%s", str);
msg->len = (unsigned int)n;
msg->type = type;
return msg;
}
static void
notif_message_free (Message *msg)
{
if (!msg)
return;
g_free (msg->payload);
g_free (msg);
}
SeafNotifManager *
seaf_notif_manager_new (SeafileSession *seaf)
{
SeafNotifManager *mgr = g_new0 (SeafNotifManager, 1);
mgr->seaf = seaf;
mgr->priv = g_new0 (SeafNotifManagerPriv, 1);
pthread_mutex_init (&mgr->priv->server_lock, NULL);
mgr->priv->servers = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, NULL);
return mgr;
}
typedef struct URI {
char *scheme;
char *host;
int port;
} URI;
// Assume that the input url format is http[s]://host:port.
static URI*
parse_notification_url (const char *url)
{
const char *server = url;
char *colon;
char *url_no_port;
char *scheme = NULL;
URI *uri = NULL;
int port;
if (strncmp(url, "https://", 8) == 0) {
scheme = g_strdup ("https");
server = url + 8;
port = 443;
} else if (strncmp (url, "http://", 7) == 0) {
scheme = g_strdup ("http");
server = url + 7;
port = 80;
}
if (!server) {
return NULL;
}
uri = g_new0 (URI, 1);
uri->scheme = scheme;
colon = strrchr (server, ':');
if (colon) {
url_no_port = g_strndup(server, colon - server);
uri->host = g_strdup (url_no_port);
if (colon + 1)
port = atoi (colon + 1);
uri->host = url_no_port;
uri->port = port;
return uri;
}
uri->host = g_strdup (server);
uri->port = port;
return uri;
}
static void
notif_server_ref (NotifServer *server);
static struct lws_context *
lws_context_new (int port);
static NotifServer*
notif_new_server (const char *server_url, gboolean use_notif_server_port)
{
NotifServer *server = NULL;
static struct lws_context *context;
URI *uri = NULL;
int port = NOTIF_PORT;
gboolean use_ssl = FALSE;
uri = parse_notification_url (server_url);
if (!uri) {
seaf_warning ("failed to parse notification url from %s\n", server_url);
return NULL;
}
// If use_notif_server_port is FALSE, the server should be deployed behind Nginx.
// In this case we'll use the same port as Seafile server.
if (!use_notif_server_port) {
port = uri->port;
}
if (strncmp(server_url, "https", 5) == 0) {
use_ssl = TRUE;
}
context = lws_context_new (use_ssl);
if (!context) {
g_free (uri->scheme);
g_free (uri->host);
g_free (uri);
seaf_warning ("failed to create libwebsockets context\n");
return NULL;
}
server = g_new0 (NotifServer, 1);
server->messages = g_async_queue_new ();
server->context = context;
server->server_url = g_strdup (server_url);
server->addr = g_strdup (uri->host);
server->use_ssl = use_ssl;
if (use_notif_server_port)
server->path = g_strdup ("/");
else
server->path = g_strdup ("/notification");
server->port = port;
pthread_mutex_init (&server->sub_lock, NULL);
server->subscriptions = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, NULL);
notif_server_ref (server);
g_free (uri->scheme);
g_free (uri->host);
g_free (uri);
return server;
}
NotifServer*
get_notif_server (SeafNotifManager *mgr, const char *url)
{
NotifServer *server = NULL;
pthread_mutex_lock (&mgr->priv->server_lock);
server = g_hash_table_lookup (mgr->priv->servers, url);
if (!server) {
pthread_mutex_unlock (&mgr->priv->server_lock);
return NULL;
}
notif_server_ref (server);
pthread_mutex_unlock (&mgr->priv->server_lock);
return server;
}
static void
delete_subscribed_repos (NotifServer *server);
static void
delete_unsent_messages (NotifServer *server);
static void
notif_server_free (NotifServer *server)
{
if (!server)
return;
if (server->context)
lws_context_destroy(server->context);
g_free (server->server_url);
g_free (server->addr);
g_free (server->path);
if (server->subscriptions)
g_hash_table_destroy (server->subscriptions);
delete_unsent_messages (server);
g_async_queue_unref (server->messages);
g_free (server);
}
static void
notif_server_ref (NotifServer *server)
{
g_atomic_int_inc (&server->refcnt);
}
static void
notif_server_unref (NotifServer *server)
{
if (!server)
return;
if (g_atomic_int_dec_and_test (&server->refcnt))
notif_server_free (server);
}
static void
init_client_connect_info (NotifServer *server);
static void *
notification_worker (void *vdata);
// This function will check whether the notification server has been created,
// if not, it will create a new one, otherwise it will return directly.
// The host is the server's url and use_notif_server_port is used to check whether the server has nginx deployed.
void
seaf_notif_manager_connect_server (SeafNotifManager *mgr, const char *host, gboolean use_notif_server_port)
{
pthread_t tid;
int rc;
NotifServer *existing_server = NULL;
NotifServer *server = NULL;
// Don't connect a connected server.
existing_server = get_notif_server (mgr, host);
if (existing_server) {
notif_server_unref (existing_server);
return;
}
server = notif_new_server (host, use_notif_server_port);
if (!server)
return;
init_client_connect_info (server);
rc = pthread_create (&tid, NULL, notification_worker, server);
if (rc != 0) {
seaf_warning ("Failed to create event notification new thread: %s.\n", strerror(rc));
notif_server_unref (server);
return;
}
pthread_mutex_lock (&mgr->priv->server_lock);
g_hash_table_insert (mgr->priv->servers, g_strdup (host), server);
pthread_mutex_unlock (&mgr->priv->server_lock);
return;
}
static void
disconnect_server (NotifServer *server)
{
// lws_cancel_service will produce a cancel event to break out of lws_service loop.
lws_cancel_service (server->context);
server->close = TRUE;
}
// This policy will send a ping packet to the server per second.
// If we don't receive pong messages within 5 seconds, it is considered that the connection is unavailable.
// We will exit the event loop, and reconnect to the notification server.
static const lws_retry_bo_t ping_policy = {
.secs_since_valid_ping = 1,
.secs_since_valid_hangup = 5,
};
static void
init_client_connect_info (NotifServer *server)
{
struct lws_client_connect_info *i = &server->i;
memset(i, 0, sizeof(server->i));
i->context = server->context;
i->port = server->port;
i->address = server->addr;
i->path = server->path;
i->host = i->address;
i->origin = i->address;
if (server->use_ssl) {
i->ssl_connection = LCCSCF_USE_SSL | LCCSCF_ALLOW_SELFSIGNED;
}
i->protocol = "notification.seafile.com";
i->local_protocol_name = "notification.seafile.com";
i->pwsi = &server->wsi;
i->retry_and_idle_policy = &ping_policy;
i->userdata = server;
}
static void
handle_messages (const char *msg, size_t len);
// success:0
static int
event_callback (struct lws *wsi, enum lws_callback_reasons reason,
void *user, void *in, size_t len)
{
NotifServer *server = (NotifServer *)user;
Message *msg = NULL;
int m;
int ret = 0;
if (!server) {
return ret;
}
seaf_debug ("Notification event: %d\n", reason);
switch (reason) {
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
server->status = STATUS_ERROR;
seaf_debug ("websocket connection error: %s\n",
in ? (char *)in : "(null)");
ret = -1;
break;
case LWS_CALLBACK_CLIENT_RECEIVE:
handle_messages (in, len);
break;
case LWS_CALLBACK_CLIENT_WRITEABLE:
msg = g_async_queue_try_pop (server->messages);
if (!msg) {
break;
}
/* notice we allowed for LWS_PRE in the payload already */
m = lws_write(wsi, ((unsigned char *)msg->payload) + LWS_PRE,
msg->len, msg->type);
if (m < (int)msg->len) {
notif_message_free (msg);
seaf_warning ("Failed to write message to websocket\n");
server->status = STATUS_ERROR;
return -1;
}
notif_message_free (msg);
break;
case LWS_CALLBACK_CLIENT_ESTABLISHED:
seaf_sync_manager_check_locks_and_folder_perms (seaf->sync_mgr, server->server_url);
server->status = STATUS_CONNECTED;
seaf_debug ("Successfully connected to the server: %s\n", server->server_url);
break;
case LWS_CALLBACK_CLIENT_CLOSED:
ret = -1;
server->status = STATUS_ERROR;
break;
case LWS_CALLBACK_EVENT_WAIT_CANCELLED:
ret = -1;
server->status = STATUS_CANCELLED;
break;
default:
break;
}
return ret;
}
static int
handle_repo_update (json_t *content)
{
json_t *member;
const char *repo_id;
const char *commit_id;
SeafRepo *repo = NULL;
int ret = 0;
member = json_object_get (content, "repo_id");
if (!member) {
seaf_warning ("Invalid repo update notification: no repo_id.\n");
return -1;
}
repo_id = json_string_value (member);
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo) {
return -1;
}
if (!seaf_notif_manager_is_repo_subscribed (seaf->notif_mgr, repo)) {
ret = -1;
goto out;
}
member = json_object_get (content, "commit_id");
if (!member) {
seaf_warning ("Invalid repo update notification: no commit_id.\n");
ret = -1;
goto out;
}
commit_id = json_string_value (member);
if (!commit_id) {
seaf_warning ("Invalid repo update notification: commit_id is null.\n");
ret = -1;
goto out;
}
seaf_repo_manager_set_repo_info_head_commit (seaf->repo_mgr,
repo->id,
commit_id);
// Set last_sync_time to 0 to allow the repo to be sync immediately.
// Otherwise it only gets synced after 30 seconds since the last sync.
seaf_sync_manager_set_last_sync_time (seaf->sync_mgr, repo->id, 0);
out:
seaf_repo_unref (repo);
return ret;
}
static int
handle_file_lock (json_t *content)
{
json_t *member;
const char *repo_id;
const char *change_event;
const char *path;
const char *lock_user;
SeafRepo *repo = NULL;
int ret = 0;
member = json_object_get (content, "repo_id");
if (!member) {
seaf_warning ("Invalid file lock notification: no repo_id.\n");
return -1;
}
repo_id = json_string_value (member);
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo) {
return -1;
}
if (!seaf_repo_manager_account_is_pro (seaf->repo_mgr, repo->server, repo->user)) {
ret = -1;
goto out;
}
if (!seaf_notif_manager_is_repo_subscribed (seaf->notif_mgr, repo)) {
ret = -1;
goto out;
}
member = json_object_get (content, "path");
if (!member) {
seaf_warning ("Invalid file lock notification: no path.\n");
ret = -1;
goto out;
}
path = json_string_value (member);
if (!path) {
seaf_warning ("Invalid repo file lock notification: path is null.\n");
ret = -1;
goto out;
}
member = json_object_get (content, "change_event");
if (!member) {
seaf_warning ("Invalid file lock notification: no change_event.\n");
ret = -1;
goto out;
}
change_event = json_string_value (member);
if (g_strcmp0 (change_event, "locked") == 0) {
member = json_object_get (content, "lock_user");
if (!member) {
seaf_warning ("Invalid file lock notification: no lock_user.\n");
ret = -1;
goto out;
}
SeafAccount *account = seaf_repo_manager_get_account (seaf->repo_mgr, repo->server, repo->user);
if (!account) {
seaf_warning ("Failed to get current account.\n");
ret = -1;
goto out;
}
lock_user = json_string_value (member);
// don't need to lock file when file has beed locked.\n
if (seaf_filelock_manager_get_lock_status (seaf->filelock_mgr, repo_id, path) != FILE_NOT_LOCKED) {
goto out;
}
FileLockType type = LOCKED_OTHERS;
if (g_strcmp0 (lock_user, account->username) == 0)
type = LOCKED_MANUAL;
seaf_filelock_manager_lock_file (seaf->filelock_mgr, repo_id, path, type);
seaf_account_free (account);
} else if (g_strcmp0 (change_event, "unlocked") == 0) {
if (seaf_filelock_manager_get_lock_status (seaf->filelock_mgr, repo_id, path) == FILE_NOT_LOCKED) {
goto out;
}
seaf_filelock_manager_mark_file_unlocked (seaf->filelock_mgr, repo_id, path);
}
out:
seaf_repo_unref (repo);
return ret;
}
static int
handle_folder_perm (json_t *content)
{
json_t *member;
const char *repo_id;
const char *change_event;
const char *type;
const char *path;
const char *permission;
SeafRepo *repo = NULL;
int ret = 0;
member = json_object_get (content, "repo_id");
if (!member) {
seaf_warning ("Invalid folder perm notification: no repo_id.\n");
return -1;
}
repo_id = json_string_value (member);
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo) {
return -1;
}
if (!seaf_repo_manager_account_is_pro (seaf->repo_mgr, repo->server, repo->user)) {
ret = -1;
goto out;
}
if (!seaf_notif_manager_is_repo_subscribed (seaf->notif_mgr, repo)) {
ret = -1;
goto out;
}
member = json_object_get (content, "path");
if (!member) {
seaf_warning ("Invalid folder perm notification: no path.\n");
ret = -1;
goto out;
}
path = json_string_value (member);
if (!path) {
seaf_warning ("Invalid repo folder perm notification: path is null.\n");
ret = -1;
goto out;
}
member = json_object_get (content, "type");
if (!member) {
seaf_warning ("Invalid folder perm notification: no type.\n");
ret = -1;
goto out;
}
type = json_string_value (member);
member = json_object_get (content, "change_event");
if (!member) {
seaf_warning ("Invalid folder perm notification: no change_event.\n");
ret = -1;
goto out;
}
change_event = json_string_value (member);
member = json_object_get (content, "perm");
if (!member) {
seaf_warning ("Invalid folder perm notification: no perm.\n");
ret = -1;
goto out;
}
permission = json_string_value (member);
FolderPerm *perm = g_new0 (FolderPerm, 1);
perm->path = g_strdup (path);
perm->permission = g_strdup (permission);
if (g_strcmp0 (type, "user") == 0) {
if (g_strcmp0 (change_event, "add") == 0 || g_strcmp0 (change_event, "modify") == 0)
seaf_repo_manager_update_folder_perm (seaf->repo_mgr, repo_id,
FOLDER_PERM_TYPE_USER,
perm);
else if (g_strcmp0 (change_event, "del") == 0)
seaf_repo_manager_delete_folder_perm (seaf->repo_mgr, repo_id,
FOLDER_PERM_TYPE_USER,
perm);
} else if (g_strcmp0 (type, "group") == 0) {
if (g_strcmp0 (change_event, "add") == 0 || g_strcmp0 (change_event, "modify") == 0)
seaf_repo_manager_update_folder_perm (seaf->repo_mgr, repo_id,
FOLDER_PERM_TYPE_GROUP,
perm);
else if (g_strcmp0 (change_event, "del") == 0)
seaf_repo_manager_delete_folder_perm (seaf->repo_mgr, repo_id,
FOLDER_PERM_TYPE_GROUP,
perm);
}
g_free (perm);
out:
seaf_repo_unref (repo);
return ret;
}
static int
handle_jwt_expired (json_t *content)
{
NotifServer *server = NULL;
json_t *member;
const char *repo_id;
SeafRepo *repo = NULL;
int ret = 0;
SeafAccount *account = NULL;
member = json_object_get (content, "repo_id");
if (!member) {
seaf_warning ("Invalid jwt expired notification: no repo_id.\n");
ret = -1;
goto out;
}
repo_id = json_string_value (member);
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo) {
ret = -1;
goto out;
}
account = seaf_repo_manager_get_account (seaf->repo_mgr, repo->server, repo->user);
if (!account) {
seaf_warning ("Failed to get current account.\n");
ret = -1;
goto out;
}
if (!seaf_notif_manager_is_repo_subscribed (seaf->notif_mgr, repo)) {
ret = -1;
goto out;
}
server = get_notif_server (seaf->notif_mgr, account->fileserver_addr);
if (!server || server->status != STATUS_CONNECTED) {
ret = -1;
goto out;
}
pthread_mutex_lock (&server->sub_lock);
g_hash_table_remove (server->subscriptions, repo_id);
pthread_mutex_unlock (&server->sub_lock);
// Set last_check_jwt_token to 0 to allow the repo to re-acquire a jwt token.
repo->last_check_jwt_token = 0;
out:
seaf_account_free (account);
notif_server_unref (server);
seaf_repo_unref (repo);
return ret;
}
static void
handle_messages (const char *msg, size_t len)
{
json_t *object, *content, *member;
json_error_t jerror;
const char *type;
seaf_debug ("Receive repo notification: %s\n", msg);
object = json_loadb (msg, len, 0, &jerror);
if (!object) {
seaf_warning ("Failed to parse notification: %s.\n", jerror.text);
return;
}
member = json_object_get (object, "type");
if (!member) {
seaf_warning ("Invalid notification info: no type.\n");
goto out;
}
type = json_string_value (member);
content = json_object_get (object, "content");
if (!content) {
seaf_warning ("Invalid notification info: no content.\n");
goto out;
}
if (g_strcmp0 (type, "repo-update") == 0) {
if (handle_repo_update (content) < 0) {
goto out;
}
} else if (g_strcmp0 (type, "file-lock-changed") == 0) {
if (handle_file_lock (content) < 0) {
goto out;
}
} else if (g_strcmp0 (type, "folder-perm-changed") == 0) {
if (handle_folder_perm (content) < 0) {
goto out;
}
} else if (g_strcmp0 (type, "jwt-expired") == 0) {
if (handle_jwt_expired (content) < 0) {
goto out;
}
}
out:
if (object)
json_decref (object);
}
static const struct lws_protocols protocols[] = {
{ "notification.seafile.com", event_callback, 0, 0, 0, NULL, 0 },
{NULL, NULL, 0, 0, 0, NULL, 0}
};
static struct lws_context *
lws_context_new (gboolean use_ssl)
{
struct lws_context_creation_info info;
struct lws_context *context = NULL;
memset(&info, 0, sizeof info);
info.port = CONTEXT_PORT_NO_LISTEN;
info.protocols = protocols;
// Since we know this lws context is only ever going to be used with
// one client wsis / fds / sockets at a time, let lws know it doesn't
// have to use the default allocations for fd tables up to ulimit -n.
// It will just allocate for 1 internal and 1 (+ 1 http2 nwsi) that we will use.
info.fd_limit_per_thread = 1 + 1 + 1;
char *ca_path = g_build_filename (seaf->seaf_dir, "ca-bundle.pem", NULL);
if (use_ssl) {
info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
info.client_ssl_ca_filepath = ca_path;
}
context = lws_create_context(&info);
if (!context) {
g_free (ca_path);
seaf_warning ("failed to create libwebsockets context\n");
return NULL;
}
g_free (ca_path);
return context;
}
static void
delete_subscribed_repos (NotifServer *server)
{
GHashTableIter iter;
gpointer key, value;
if (!server->subscriptions)
return;
pthread_mutex_lock (&server->sub_lock);
g_hash_table_iter_init (&iter, server->subscriptions);
while (g_hash_table_iter_next (&iter, &key, &value)) {
g_hash_table_iter_remove (&iter);
}
pthread_mutex_unlock (&server->sub_lock);
}
static void
delete_unsent_messages (NotifServer *server)
{
Message *msg = NULL;
if (!server->messages)
return;
while (1) {
msg = g_async_queue_try_pop (server->messages);
if (!msg) {
break;
}
notif_message_free (msg);
}
return;
}
static void *
notification_worker (void *vdata)
{
NotifServer *server = (NotifServer *)vdata;
if (!server) {
return 0;
}
struct lws_client_connect_info *i = &server->i;
int n = 0;
while (!server->close) {
// We don't need to check the return value of this function, the connection will be processed in the event loop.
lws_client_connect_via_info(i);
while (n >= 0 && !server->close &&
server->status != STATUS_ERROR &&
server->status != STATUS_CANCELLED) {
n = lws_service(server->context, 0);
}
delete_subscribed_repos (server);
delete_unsent_messages (server);
if (server->status == STATUS_CANCELLED)
break;
// Wait a minute to reconnect to the notification server.
seaf_sleep (RECONNECT_INTERVAL);
n = 0;
server->status = STATUS_DISCONNECTED;
}
seaf_message ("Notification worker for server %s exiting.\n", server->server_url);
pthread_mutex_lock (&seaf->notif_mgr->priv->server_lock);
g_hash_table_remove (seaf->notif_mgr->priv->servers, server->server_url);
pthread_mutex_unlock (&seaf->notif_mgr->priv->server_lock);
notif_server_unref (server);
return 0;
}
void
seaf_notif_manager_subscribe_repo (SeafNotifManager *mgr, SeafRepo *repo)
{
NotifServer *server = NULL;
json_t *json_msg = NULL;
json_t *content = NULL;
char *str = NULL;
char *sub_id = NULL;
json_t *array, *obj;
SeafAccount *account = NULL;
account = seaf_repo_manager_get_account (seaf->repo_mgr, repo->server, repo->user);
if (!account) {
goto out;
}
server = get_notif_server (mgr, account->fileserver_addr);
if (!server || server->status != STATUS_CONNECTED)
goto out;
json_msg = json_object ();
json_object_set_new (json_msg, "type", json_string("subscribe"));
content = json_object ();
array = json_array ();
obj = json_object ();
json_object_set_new (obj, "id", json_string(repo->id));
json_object_set_new (obj, "jwt_token", json_string(repo->jwt_token));
json_array_append_new (array, obj);
json_object_set_new (content, "repos", array);
json_object_set_new (json_msg, "content", content);
str = json_dumps (json_msg, JSON_COMPACT);
if (!str)
goto out;
Message *msg = notif_message_new (str, LWS_WRITE_TEXT);
if (!msg)
goto out;
g_async_queue_push (server->messages, msg);
sub_id = g_strdup (repo->id);
pthread_mutex_lock (&server->sub_lock);
g_hash_table_insert (server->subscriptions, sub_id, sub_id);
pthread_mutex_unlock (&server->sub_lock);
seaf_debug ("Successfully subscribe repo %s\n", repo->id);
out:
seaf_account_free (account);
g_free (str);
json_decref (json_msg);
notif_server_unref (server);
}
void
seaf_notif_manager_unsubscribe_repo (SeafNotifManager *mgr, SeafRepo *repo)
{
NotifServer *server = NULL;
json_t *json_msg = NULL;
json_t *content = NULL;
char *str = NULL;
json_t *array, *obj;
SeafAccount *account = NULL;
account = seaf_repo_manager_get_account (seaf->repo_mgr, repo->server, repo->user);
if (!account) {
goto out;
}
server = get_notif_server (mgr, account->fileserver_addr);
if (!server || server->status != STATUS_CONNECTED) {
goto out;
}
json_msg = json_object ();
json_object_set_new (json_msg, "type", json_string("unsubscribe"));
content = json_object ();
array = json_array ();
obj = json_object ();
json_object_set_new (obj, "id", json_string(repo->id));
json_array_append_new (array, obj);
json_object_set_new (content, "repos", array);
json_object_set_new (json_msg, "content", content);
str = json_dumps (json_msg, JSON_COMPACT);
if (!str)
goto out;
Message *msg = notif_message_new (str, LWS_WRITE_TEXT);
if (!msg)
goto out;
g_async_queue_push (server->messages, msg);
pthread_mutex_lock (&server->sub_lock);
g_hash_table_remove (server->subscriptions, repo->id);
pthread_mutex_unlock (&server->sub_lock);
seaf_debug ("Successfully unsubscribe repo %s\n", repo->id);
out:
seaf_account_free (account);
g_free (str);
json_decref (json_msg);
notif_server_unref (server);
}
gboolean
seaf_notif_manager_is_repo_subscribed (SeafNotifManager *mgr, SeafRepo *repo)
{
NotifServer *server = NULL;
gboolean subscribed = FALSE;
SeafAccount *account = NULL;
account = seaf_repo_manager_get_account (seaf->repo_mgr, repo->server, repo->user);
if (!account) {
goto out;
}
server = get_notif_server (mgr, account->fileserver_addr);
if (!server || server->status != STATUS_CONNECTED) {
goto out;
}
pthread_mutex_lock (&server->sub_lock);
if (g_hash_table_lookup (server->subscriptions, repo->id)) {
pthread_mutex_unlock (&server->sub_lock);
subscribed = TRUE;
goto out;
}
pthread_mutex_unlock (&server->sub_lock);
out:
seaf_account_free (account);
notif_server_unref (server);
return subscribed;
}
seadrive-fuse-3.0.13/src/notif-mgr.h 0000664 0000000 0000000 00000001510 14761776747 0017231 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#ifndef NOTIF_MGR_H
#define NOTIF_MGR_H
typedef struct _SeafNotifManager SeafNotifManager;
typedef struct _SeafNotifManagerPriv SeafNotifManagerPriv;
struct _SeafileSession;
struct _SeafNotifManager {
struct _SeafileSession *seaf;
SeafNotifManagerPriv *priv;
};
SeafNotifManager *
seaf_notif_manager_new (struct _SeafileSession *seaf);
void
seaf_notif_manager_connect_server (SeafNotifManager *mgr, const char *host,
gboolean use_notif_server_port);
void
seaf_notif_manager_subscribe_repo (SeafNotifManager *mgr, SeafRepo *repo);
void
seaf_notif_manager_unsubscribe_repo (SeafNotifManager *mgr, SeafRepo *repo);
gboolean
seaf_notif_manager_is_repo_subscribed (SeafNotifManager *mgr, SeafRepo *repo);
#endif
seadrive-fuse-3.0.13/src/obj-backend-fs.c 0000664 0000000 0000000 00000026572 14761776747 0020106 0 ustar 00root root 0000000 0000000 #include "common.h"
#include "utils.h"
#include "obj-backend.h"
#include
#include
#include
#define DEBUG_FLAG SEAFILE_DEBUG_OTHER
#include "log.h"
typedef struct FsPriv {
char *v0_obj_dir;
int v0_dir_len;
char *obj_dir;
int dir_len;
} FsPriv;
static void
id_to_path (FsPriv *priv, const char *obj_id, char path[],
const char *repo_id, int version)
{
char *pos = path;
int n;
if (version > 0) {
n = snprintf (path, SEAF_PATH_MAX, "%s/%s/", priv->obj_dir, repo_id);
pos += n;
} else {
memcpy (pos, priv->v0_obj_dir, priv->v0_dir_len);
pos[priv->v0_dir_len] = '/';
pos += priv->v0_dir_len + 1;
}
memcpy (pos, obj_id, 2);
pos[2] = '/';
pos += 3;
memcpy (pos, obj_id + 2, 41 - 2);
}
static int
obj_backend_fs_read (ObjBackend *bend,
const char *repo_id,
int version,
const char *obj_id,
void **data,
int *len)
{
char path[SEAF_PATH_MAX];
gsize tmp_len;
GError *error = NULL;
id_to_path (bend->priv, obj_id, path, repo_id, version);
/* seaf_debug ("object path: %s\n", path); */
g_file_get_contents (path, (gchar**)data, &tmp_len, &error);
if (error) {
#ifdef MIGRATION
g_clear_error (&error);
id_to_path (bend->priv, obj_id, path, repo_id, 1);
g_file_get_contents (path, (gchar**)data, &tmp_len, &error);
if (error) {
seaf_debug ("[obj backend] Failed to read object %s: %s.\n",
obj_id, error->message);
g_clear_error (&error);
return -1;
}
#else
seaf_debug ("[obj backend] Failed to read object %s: %s.\n",
obj_id, error->message);
g_clear_error (&error);
return -1;
#endif
}
*len = (int)tmp_len;
return 0;
}
/*
* Flush operating system and disk caches for @fd.
*/
static int
fsync_obj_contents (int fd)
{
#ifdef __linux__
/* Some file systems may not support fsync().
* In this case, just skip the error.
*/
if (fsync (fd) < 0) {
if (errno == EINVAL)
return 0;
else {
seaf_warning ("Failed to fsync: %s.\n", strerror(errno));
return -1;
}
}
return 0;
#endif
#ifdef __APPLE__
/* OS X: fcntl() is required to flush disk cache, fsync() only
* flushes operating system cache.
*/
if (fcntl (fd, F_FULLFSYNC, NULL) < 0) {
seaf_warning ("Failed to fsync: %s.\n", strerror(errno));
return -1;
}
return 0;
#endif
}
/*
* Rename file from @tmp_path to @obj_path.
* This also makes sure the changes to @obj_path's parent folder
* is flushed to disk.
*/
static int
rename_and_sync (const char *tmp_path, const char *obj_path)
{
#ifdef __linux__
char *parent_dir;
int ret = 0;
if (rename (tmp_path, obj_path) < 0) {
seaf_warning ("Failed to rename from %s to %s: %s.\n",
tmp_path, obj_path, strerror(errno));
return -1;
}
parent_dir = g_path_get_dirname (obj_path);
int dir_fd = open (parent_dir, O_RDONLY);
if (dir_fd < 0) {
seaf_warning ("Failed to open dir %s: %s.\n", parent_dir, strerror(errno));
goto out;
}
/* Some file systems don't support fsyncing a directory. Just ignore the error.
*/
if (fsync (dir_fd) < 0) {
if (errno != EINVAL) {
seaf_warning ("Failed to fsync dir %s: %s.\n",
parent_dir, strerror(errno));
ret = -1;
}
goto out;
}
out:
g_free (parent_dir);
if (dir_fd >= 0)
close (dir_fd);
return ret;
#endif
#ifdef __APPLE__
/*
* OS X garantees an existence of obj_path always exists,
* even when the system crashes.
*/
if (rename (tmp_path, obj_path) < 0) {
seaf_warning ("Failed to rename from %s to %s: %s.\n",
tmp_path, obj_path, strerror(errno));
return -1;
}
return 0;
#endif
}
static int
save_obj_contents (const char *path, const void *data, int len, gboolean need_sync)
{
char tmp_path[SEAF_PATH_MAX];
int fd;
snprintf (tmp_path, SEAF_PATH_MAX, "%s.XXXXXX", path);
fd = g_mkstemp (tmp_path);
if (fd < 0) {
seaf_warning ("[obj backend] Failed to open tmp file %s: %s.\n",
tmp_path, strerror(errno));
return -1;
}
if (writen (fd, data, len) < 0) {
seaf_warning ("[obj backend] Failed to write obj %s: %s.\n",
tmp_path, strerror(errno));
return -1;
}
if (need_sync && fsync_obj_contents (fd) < 0)
return -1;
/* Close may return error, especially in NFS. */
if (close (fd) < 0) {
seaf_warning ("[obj backend Failed close obj %s: %s.\n",
tmp_path, strerror(errno));
return -1;
}
if (need_sync) {
if (rename_and_sync (tmp_path, path) < 0)
return -1;
} else {
if (g_file_test(path, G_FILE_TEST_EXISTS)) {
if (g_unlink (path) < 0) {
seaf_warning ("[obj bend] failed to remove existing obj %s: %s\n",
path, strerror(errno));
}
}
if (g_rename (tmp_path, path) < 0) {
seaf_warning ("[obj backend] Failed to rename %s: %s.\n",
path, strerror(errno));
return -1;
}
}
return 0;
}
static int
create_parent_path (const char *path)
{
char *dir = g_path_get_dirname (path);
if (!dir)
return -1;
if (g_file_test (dir, G_FILE_TEST_EXISTS)) {
g_free (dir);
return 0;
}
if (g_mkdir_with_parents (dir, 0777) < 0) {
seaf_warning ("Failed to create object parent path %s: %s.\n",
dir, strerror(errno));
g_free (dir);
return -1;
}
g_free (dir);
return 0;
}
static int
obj_backend_fs_write (ObjBackend *bend,
const char *repo_id,
int version,
const char *obj_id,
void *data,
int len,
gboolean need_sync)
{
char path[SEAF_PATH_MAX];
id_to_path (bend->priv, obj_id, path, repo_id, version);
/* GTimeVal s, e; */
/* g_get_current_time (&s); */
if (create_parent_path (path) < 0) {
seaf_warning ("[obj backend] Failed to create path for obj %s:%s.\n",
repo_id, obj_id);
return -1;
}
if (save_obj_contents (path, data, len, need_sync) < 0) {
seaf_warning ("[obj backend] Failed to write obj %s:%s.\n",
repo_id, obj_id);
return -1;
}
/* g_get_current_time (&e); */
/* seaf_message ("write obj time: %ldus.\n", */
/* ((e.tv_sec*1000000+e.tv_usec) - (s.tv_sec*1000000+s.tv_usec))); */
return 0;
}
static gboolean
obj_backend_fs_exists (ObjBackend *bend,
const char *repo_id,
int version,
const char *obj_id)
{
char path[SEAF_PATH_MAX];
SeafStat st;
id_to_path (bend->priv, obj_id, path, repo_id, version);
if (seaf_stat (path, &st) == 0)
return TRUE;
return FALSE;
}
static void
obj_backend_fs_delete (ObjBackend *bend,
const char *repo_id,
int version,
const char *obj_id)
{
char path[SEAF_PATH_MAX];
id_to_path (bend->priv, obj_id, path, repo_id, version);
g_unlink (path);
}
static int
obj_backend_fs_foreach_obj (ObjBackend *bend,
const char *repo_id,
int version,
SeafObjFunc process,
void *user_data)
{
FsPriv *priv = bend->priv;
char *obj_dir = NULL;
int dir_len;
GDir *dir1 = NULL, *dir2;
const char *dname1, *dname2;
char obj_id[128];
char path[SEAF_PATH_MAX], *pos;
int ret = 0;
if (version > 0)
obj_dir = g_build_filename (priv->obj_dir, repo_id, NULL);
else
obj_dir = g_strdup(priv->v0_obj_dir);
dir_len = strlen (obj_dir);
dir1 = g_dir_open (obj_dir, 0, NULL);
if (!dir1) {
goto out;
}
memcpy (path, obj_dir, dir_len);
pos = path + dir_len;
while ((dname1 = g_dir_read_name(dir1)) != NULL) {
snprintf (pos, sizeof(path) - dir_len, "/%s", dname1);
dir2 = g_dir_open (path, 0, NULL);
if (!dir2) {
seaf_warning ("Failed to open object dir %s.\n", path);
continue;
}
while ((dname2 = g_dir_read_name(dir2)) != NULL) {
snprintf (obj_id, sizeof(obj_id), "%s%s", dname1, dname2);
if (!process (repo_id, version, obj_id, user_data)) {
g_dir_close (dir2);
goto out;
}
}
g_dir_close (dir2);
}
out:
if (dir1)
g_dir_close (dir1);
g_free (obj_dir);
return ret;
}
static int
obj_backend_fs_remove_store (ObjBackend *bend, const char *store_id)
{
FsPriv *priv = bend->priv;
char *obj_dir = NULL;
GDir *dir1, *dir2;
const char *dname1, *dname2;
char *path1, *path2;
obj_dir = g_build_filename (priv->obj_dir, store_id, NULL);
dir1 = g_dir_open (obj_dir, 0, NULL);
if (!dir1) {
g_free (obj_dir);
return 0;
}
while ((dname1 = g_dir_read_name(dir1)) != NULL) {
path1 = g_build_filename (obj_dir, dname1, NULL);
dir2 = g_dir_open (path1, 0, NULL);
if (!dir2) {
seaf_warning ("Failed to open obj dir %s.\n", path1);
g_dir_close (dir1);
g_free (path1);
g_free (obj_dir);
return -1;
}
while ((dname2 = g_dir_read_name(dir2)) != NULL) {
path2 = g_build_filename (path1, dname2, NULL);
g_unlink (path2);
g_free (path2);
}
g_dir_close (dir2);
g_rmdir (path1);
g_free (path1);
}
g_dir_close (dir1);
g_rmdir (obj_dir);
g_free (obj_dir);
return 0;
}
ObjBackend *
obj_backend_fs_new (const char *seaf_dir, const char *obj_type)
{
ObjBackend *bend;
FsPriv *priv;
bend = g_new0(ObjBackend, 1);
priv = g_new0(FsPriv, 1);
bend->priv = priv;
priv->v0_obj_dir = g_build_filename (seaf_dir, obj_type, NULL);
priv->v0_dir_len = strlen(priv->v0_obj_dir);
priv->obj_dir = g_build_filename (seaf_dir, "storage", obj_type, NULL);
priv->dir_len = strlen (priv->obj_dir);
if (g_mkdir_with_parents (priv->v0_obj_dir, 0777) < 0) {
seaf_warning ("[Obj Backend] Objects dir %s does not exist and"
" is unable to create\n", priv->v0_obj_dir);
goto onerror;
}
if (g_mkdir_with_parents (priv->obj_dir, 0777) < 0) {
seaf_warning ("[Obj Backend] Objects dir %s does not exist and"
" is unable to create\n", priv->obj_dir);
goto onerror;
}
bend->read = obj_backend_fs_read;
bend->write = obj_backend_fs_write;
bend->exists = obj_backend_fs_exists;
bend->delete = obj_backend_fs_delete;
bend->foreach_obj = obj_backend_fs_foreach_obj;
bend->remove_store = obj_backend_fs_remove_store;
return bend;
onerror:
g_free (priv->v0_obj_dir);
g_free (priv->obj_dir);
g_free (priv);
g_free (bend);
return NULL;
}
seadrive-fuse-3.0.13/src/obj-backend.h 0000664 0000000 0000000 00000002637 14761776747 0017501 0 ustar 00root root 0000000 0000000 #ifndef OBJ_BACKEND_H
#define OBJ_BACKEND_H
#include
#include "obj-store.h"
typedef struct ObjBackend ObjBackend;
struct ObjBackend {
int (*read) (ObjBackend *bend,
const char *repo_id,
int version,
const char *obj_id,
void **data,
int *len);
int (*write) (ObjBackend *bend,
const char *repo_id,
int version,
const char *obj_id,
void *data,
int len,
gboolean need_sync);
gboolean (*exists) (ObjBackend *bend,
const char *repo_id,
int version,
const char *obj_id);
void (*delete) (ObjBackend *bend,
const char *repo_id,
int version,
const char *obj_id);
int (*foreach_obj) (ObjBackend *bend,
const char *repo_id,
int version,
SeafObjFunc process,
void *user_data);
int (*remove_store) (ObjBackend *bend,
const char *store_id);
void *priv;
};
#endif
seadrive-fuse-3.0.13/src/obj-store.c 0000664 0000000 0000000 00000006260 14761776747 0017235 0 ustar 00root root 0000000 0000000 #include "common.h"
#include "log.h"
#include "seafile-session.h"
#include "utils.h"
#include "obj-backend.h"
#include "obj-store.h"
struct SeafObjStore {
ObjBackend *bend;
};
typedef struct SeafObjStore SeafObjStore;
extern ObjBackend *
obj_backend_fs_new (const char *seaf_dir, const char *obj_type);
struct SeafObjStore *
seaf_obj_store_new (SeafileSession *seaf, const char *obj_type)
{
SeafObjStore *store = g_new0 (SeafObjStore, 1);
if (!store)
return NULL;
store->bend = obj_backend_fs_new (seaf->seaf_dir, obj_type);
if (!store->bend) {
seaf_warning ("[Object store] Failed to load backend.\n");
g_free (store);
return NULL;
}
return store;
}
int
seaf_obj_store_init (SeafObjStore *obj_store)
{
return 0;
}
int
seaf_obj_store_read_obj (struct SeafObjStore *obj_store,
const char *repo_id,
int version,
const char *obj_id,
void **data,
int *len)
{
ObjBackend *bend = obj_store->bend;
if (!repo_id || !is_uuid_valid(repo_id) ||
!obj_id || !is_object_id_valid(obj_id))
return -1;
return bend->read (bend, repo_id, version, obj_id, data, len);
}
int
seaf_obj_store_write_obj (struct SeafObjStore *obj_store,
const char *repo_id,
int version,
const char *obj_id,
void *data,
int len,
gboolean need_sync)
{
ObjBackend *bend = obj_store->bend;
if (!repo_id || !is_uuid_valid(repo_id) ||
!obj_id || !is_object_id_valid(obj_id))
return -1;
return bend->write (bend, repo_id, version, obj_id, data, len, need_sync);
}
gboolean
seaf_obj_store_obj_exists (struct SeafObjStore *obj_store,
const char *repo_id,
int version,
const char *obj_id)
{
ObjBackend *bend = obj_store->bend;
if (!repo_id || !is_uuid_valid(repo_id) ||
!obj_id || !is_object_id_valid(obj_id))
return FALSE;
return bend->exists (bend, repo_id, version, obj_id);
}
void
seaf_obj_store_delete_obj (struct SeafObjStore *obj_store,
const char *repo_id,
int version,
const char *obj_id)
{
ObjBackend *bend = obj_store->bend;
if (!repo_id || !is_uuid_valid(repo_id) ||
!obj_id || !is_object_id_valid(obj_id))
return;
bend->delete (bend, repo_id, version, obj_id);
}
int
seaf_obj_store_foreach_obj (struct SeafObjStore *obj_store,
const char *repo_id,
int version,
SeafObjFunc process,
void *user_data)
{
ObjBackend *bend = obj_store->bend;
return bend->foreach_obj (bend, repo_id, version, process, user_data);
}
int
seaf_obj_store_remove_store (struct SeafObjStore *obj_store,
const char *store_id)
{
ObjBackend *bend = obj_store->bend;
return bend->remove_store (bend, store_id);
}
seadrive-fuse-3.0.13/src/obj-store.h 0000664 0000000 0000000 00000003533 14761776747 0017242 0 ustar 00root root 0000000 0000000 #ifndef OBJ_STORE_H
#define OBJ_STORE_H
#include
#include
struct _SeafileSession;
struct SeafObjStore;
struct SeafObjStore *
seaf_obj_store_new (struct _SeafileSession *seaf, const char *obj_type);
int
seaf_obj_store_init (struct SeafObjStore *obj_store);
/* Synchronous I/O interface. */
int
seaf_obj_store_read_obj (struct SeafObjStore *obj_store,
const char *repo_id,
int version,
const char *obj_id,
void **data,
int *len);
int
seaf_obj_store_write_obj (struct SeafObjStore *obj_store,
const char *repo_id,
int version,
const char *obj_id,
void *data,
int len,
gboolean need_sync);
gboolean
seaf_obj_store_obj_exists (struct SeafObjStore *obj_store,
const char *repo_id,
int version,
const char *obj_id);
void
seaf_obj_store_delete_obj (struct SeafObjStore *obj_store,
const char *repo_id,
int version,
const char *obj_id);
typedef gboolean (*SeafObjFunc) (const char *repo_id,
int version,
const char *obj_id,
void *user_data);
int
seaf_obj_store_foreach_obj (struct SeafObjStore *obj_store,
const char *repo_id,
int version,
SeafObjFunc process,
void *user_data);
int
seaf_obj_store_remove_store (struct SeafObjStore *obj_store,
const char *store_id);
#endif
seadrive-fuse-3.0.13/src/password-hash.c 0000664 0000000 0000000 00000012147 14761776747 0020115 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#include "common.h"
#include
#include
#include
#include "password-hash.h"
#include "seafile-crypt.h"
#ifdef USE_GPL_CRYPTO
#include
#include
#include
#else
#include
#include
#include
#endif
#include "utils.h"
#include "log.h"
// pbkdf2
typedef struct Pbkdf2Params {
int iteration;
} Pbkdf2Params;
static Pbkdf2Params *
parse_pbkdf2_sha256_params (const char *params_str)
{
Pbkdf2Params *params = NULL;
if (!params_str) {
params = g_new0 (Pbkdf2Params, 1);
params->iteration = 1000;
return params;
}
int iteration;
iteration = atoi (params_str);
if (iteration <= 0) {
iteration = 1000;
}
params = g_new0 (Pbkdf2Params, 1);
params->iteration = iteration;
return params;
}
static int
pbkdf2_sha256_derive_key (const char *data_in, int in_len,
const char *salt,
Pbkdf2Params *params,
unsigned char *key)
{
int iteration = params->iteration;
unsigned char salt_bin[32] = {0};
hex_to_rawdata (salt, salt_bin, 32);
#ifdef USE_GPL_CRYPTO
pbkdf2_hmac_sha256 (in_len, (const guchar *)data_in, iteration,
sizeof(salt_bin), salt_bin, 32, key);
#else
PKCS5_PBKDF2_HMAC (data_in, in_len,
salt_bin, sizeof(salt_bin),
iteration,
EVP_sha256(),
32, key);
#endif
return 0;
}
// argon2id
typedef struct Argon2idParams{
gint64 time_cost;
gint64 memory_cost;
gint64 parallelism;
} Argon2idParams;
// The arguments to argon2 are separated by commas.
// Example arguments format:
// 2,102400,8
// The parameters are time_cost, memory_cost, parallelism from left to right.
static Argon2idParams *
parse_argon2id_params (const char *params_str)
{
char **params;
Argon2idParams *argon2_params = g_new0 (Argon2idParams, 1);
if (params_str)
params = g_strsplit (params_str, ",", 3);
if (!params_str || g_strv_length(params) != 3) {
if (params_str)
g_strfreev (params);
argon2_params->time_cost = 2; // 2-pass computation
argon2_params->memory_cost = 102400; // 100 mebibytes memory usage
argon2_params->parallelism = 8; // number of threads and lanes
return argon2_params;
}
char *p = NULL;
p = g_strstrip (params[0]);
argon2_params->time_cost = atoll (p);
if (argon2_params->time_cost <= 0) {
argon2_params->time_cost = 2;
}
p = g_strstrip (params[1]);
argon2_params->memory_cost = atoll (p);
if (argon2_params->memory_cost <= 0) {
argon2_params->memory_cost = 102400;
}
p = g_strstrip (params[2]);
argon2_params->parallelism = atoll (p);
if (argon2_params->parallelism <= 0) {
argon2_params->parallelism = 8;
}
g_strfreev (params);
return argon2_params;
}
static int
argon2id_derive_key (const char *data_in, int in_len,
const char *salt,
Argon2idParams *params,
unsigned char *key)
{
unsigned char salt_bin[32] = {0};
hex_to_rawdata (salt, salt_bin, 32);
argon2id_hash_raw(params->time_cost, params->memory_cost, params->parallelism,
data_in, in_len,
salt_bin, sizeof(salt_bin),
key, 32);
return 0;
}
// parse_pwd_hash_params is used to parse default pwd hash algorithms.
void
parse_pwd_hash_params (const char *algo, const char *params_str, PwdHashParams *params)
{
if (g_strcmp0 (algo, PWD_HASH_PDKDF2) == 0) {
params->algo = g_strdup (PWD_HASH_PDKDF2);
if (params_str)
params->params_str = g_strdup (params_str);
else
params->params_str = g_strdup ("1000");
} else if (g_strcmp0 (algo, PWD_HASH_ARGON2ID) == 0) {
params->algo = g_strdup (PWD_HASH_ARGON2ID);
if (params_str)
params->params_str = g_strdup (params_str);
else
params->params_str = g_strdup ("2,102400,8");
} else {
params->algo = NULL;
}
seaf_message ("password hash algorithms: %s, params: %s\n ", params->algo, params->params_str);
}
int
pwd_hash_derive_key (const char *data_in, int in_len,
const char *salt,
const char *algo, const char *params_str,
unsigned char *key)
{
int ret = 0;
if (g_strcmp0 (algo, PWD_HASH_ARGON2ID) == 0) {
Argon2idParams *algo_params = parse_argon2id_params (params_str);
ret = argon2id_derive_key (data_in, in_len,
salt, algo_params, key);
g_free (algo_params);
return ret;
} else {
Pbkdf2Params *algo_params = parse_pbkdf2_sha256_params (params_str);
ret = pbkdf2_sha256_derive_key (data_in, in_len,
salt, algo_params, key);
g_free (algo_params);
return ret;
}
}
seadrive-fuse-3.0.13/src/password-hash.h 0000664 0000000 0000000 00000001136 14761776747 0020116 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#ifndef _PASSWORD_HASH_H
#define _PASSWORD_HASH_H
#define PWD_HASH_PDKDF2 "pbkdf2_sha256"
#define PWD_HASH_ARGON2ID "argon2id"
typedef struct _PwdHashParams {
char *algo;
char *params_str;
} PwdHashParams;
void
parse_pwd_hash_params (const char *algo, const char *params_str, PwdHashParams *params);
int
pwd_hash_derive_key (const char *data_in, int in_len,
const char *repo_salt,
const char *algo, const char *params_str,
unsigned char *key);
#endif
seadrive-fuse-3.0.13/src/rabin-checksum.c 0000664 0000000 0000000 00000007136 14761776747 0020227 0 ustar 00root root 0000000 0000000 #include
#include "rabin-checksum.h"
#define INT64(n) n##LL
#define MSB64 INT64(0x8000000000000000)
static u_int64_t poly = 0xbfe6b8a5bf378d83LL;
static u_int64_t T[256];
static u_int64_t U[256];
static int shift;
/* Highest bit set in a byte */
static const char bytemsb[0x100] = {
0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
};
/* Find last set (most significant bit) */
static inline u_int fls32 (u_int32_t v)
{
if (v & 0xffff0000) {
if (v & 0xff000000)
return 24 + bytemsb[v>>24];
else
return 16 + bytemsb[v>>16];
}
if (v & 0x0000ff00)
return 8 + bytemsb[v>>8];
else
return bytemsb[v];
}
static inline char fls64 (u_int64_t v)
{
u_int32_t h;
if ((h = v >> 32))
return 32 + fls32 (h);
else
return fls32 ((u_int32_t) v);
}
u_int64_t polymod (u_int64_t nh, u_int64_t nl, u_int64_t d)
{
int i = 0;
int k = fls64 (d) - 1;
d <<= 63 - k;
if (nh) {
if (nh & MSB64)
nh ^= d;
for (i = 62; i >= 0; i--)
if (nh & ((u_int64_t) 1) << i) {
nh ^= d >> (63 - i);
nl ^= d << (i + 1);
}
}
for (i = 63; i >= k; i--)
{
if (nl & INT64 (1) << i)
nl ^= d >> (63 - i);
}
return nl;
}
void polymult (u_int64_t *php, u_int64_t *plp, u_int64_t x, u_int64_t y)
{
int i;
u_int64_t ph = 0, pl = 0;
if (x & 1)
pl = y;
for (i = 1; i < 64; i++)
if (x & (INT64 (1) << i)) {
ph ^= y >> (64 - i);
pl ^= y << i;
}
if (php)
*php = ph;
if (plp)
*plp = pl;
}
u_int64_t polymmult (u_int64_t x, u_int64_t y, u_int64_t d)
{
u_int64_t h, l;
polymult (&h, &l, x, y);
return polymod (h, l, d);
}
static u_int64_t append8 (u_int64_t p, u_char m)
{
return ((p << 8) | m) ^ T[p >> shift];
}
static void calcT (u_int64_t poly)
{
int j = 0;
int xshift = fls64 (poly) - 1;
shift = xshift - 8;
u_int64_t T1 = polymod (0, INT64 (1) << xshift, poly);
for (j = 0; j < 256; j++) {
T[j] = polymmult (j, T1, poly) | ((u_int64_t) j << xshift);
}
}
static void calcU(int size)
{
int i;
u_int64_t sizeshift = 1;
for (i = 1; i < size; i++)
sizeshift = append8 (sizeshift, 0);
for (i = 0; i < 256; i++)
U[i] = polymmult (i, sizeshift, poly);
}
void rabin_init(int len)
{
calcT(poly);
calcU(len);
}
/*
* a simple 32 bit checksum that can be upadted from end
*/
unsigned int rabin_checksum(char *buf, int len)
{
int i;
unsigned int sum = 0;
for (i = 0; i < len; ++i) {
sum = rabin_rolling_checksum (sum, len, 0, buf[i]);
}
return sum;
}
unsigned int rabin_rolling_checksum(unsigned int csum, int len,
char c1, char c2)
{
return append8(csum ^ U[(unsigned char)c1], c2);
}
seadrive-fuse-3.0.13/src/rabin-checksum.h 0000664 0000000 0000000 00000000336 14761776747 0020227 0 ustar 00root root 0000000 0000000 #ifndef _RABIN_CHECKSUM_H
#define _RABIN_CHECKSUM_H
unsigned int rabin_checksum(char *buf, int len);
unsigned int rabin_rolling_checksum(unsigned int csum, int len, char c1, char c2);
void rabin_init (int len);
#endif
seadrive-fuse-3.0.13/src/repo-mgr.c 0000664 0000000 0000000 00000400117 14761776747 0017060 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#include "common.h"
#include
#include
#include "utils.h"
#define DEBUG_FLAG SEAFILE_DEBUG_SYNC
#include "log.h"
#include "seafile-session.h"
#include "seafile-config.h"
#include "commit-mgr.h"
#include "branch-mgr.h"
#include "repo-mgr.h"
#include "fs-mgr.h"
#include "seafile-error.h"
#include "seafile-crypt.h"
#include "db.h"
struct _SeafRepoManagerPriv {
GHashTable *repo_hash;
pthread_rwlock_t lock;
/* Cache of repos for current account. */
// The key is 'server_username', the value is server and user of account.
GHashTable *accounts;
// The key is repo id, the value is repo info.
GHashTable *repo_infos;
// The key is 'server_username', the value a hash table of name_to_repo.
GHashTable *repo_names;
pthread_rwlock_t account_lock;
sqlite3 *db;
pthread_mutex_t db_lock;
GHashTable *user_perms; /* repo_id -> folder user perms */
GHashTable *group_perms; /* repo_id -> folder group perms */
pthread_mutex_t perm_lock;
};
static SeafRepo *
load_repo (SeafRepoManager *manager, const char *repo_id);
static void load_repos (SeafRepoManager *manager, const char *seaf_dir);
static void seaf_repo_manager_del_repo_property (SeafRepoManager *manager,
const char *repo_id);
static int save_branch_repo_map (SeafRepoManager *manager, SeafBranch *branch);
static void save_repo_property (SeafRepoManager *manager,
const char *repo_id,
const char *key, const char *value);
static void
locked_file_free (LockedFile *file)
{
if (!file)
return;
g_free (file->operation);
g_free (file);
}
static gboolean
load_locked_file (sqlite3_stmt *stmt, void *data)
{
GHashTable *ret = data;
LockedFile *file;
const char *path, *operation, *file_id;
gint64 old_mtime;
path = (const char *)sqlite3_column_text (stmt, 0);
operation = (const char *)sqlite3_column_text (stmt, 1);
old_mtime = sqlite3_column_int64 (stmt, 2);
file_id = (const char *)sqlite3_column_text (stmt, 3);
file = g_new0 (LockedFile, 1);
file->operation = g_strdup(operation);
file->old_mtime = old_mtime;
if (file_id)
memcpy (file->file_id, file_id, 40);
g_hash_table_insert (ret, g_strdup(path), file);
return TRUE;
}
LockedFileSet *
seaf_repo_manager_get_locked_file_set (SeafRepoManager *mgr, const char *repo_id)
{
GHashTable *locked_files = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free,
(GDestroyNotify)locked_file_free);
char sql[256];
sqlite3_snprintf (sizeof(sql), sql,
"SELECT path, operation, old_mtime, file_id FROM LockedFiles "
"WHERE repo_id = '%q'",
repo_id);
pthread_mutex_lock (&mgr->priv->db_lock);
/* Ingore database error. We return an empty set on error. */
sqlite_foreach_selected_row (mgr->priv->db, sql,
load_locked_file, locked_files);
pthread_mutex_unlock (&mgr->priv->db_lock);
LockedFileSet *ret = g_new0 (LockedFileSet, 1);
ret->mgr = mgr;
memcpy (ret->repo_id, repo_id, 36);
ret->locked_files = locked_files;
return ret;
}
void
locked_file_set_free (LockedFileSet *fset)
{
if (!fset)
return;
g_hash_table_destroy (fset->locked_files);
g_free (fset);
}
int
locked_file_set_add_update (LockedFileSet *fset,
const char *path,
const char *operation,
gint64 old_mtime,
const char *file_id)
{
SeafRepoManager *mgr = fset->mgr;
char *sql;
sqlite3_stmt *stmt;
LockedFile *file;
gboolean exists;
exists = (g_hash_table_lookup (fset->locked_files, path) != NULL);
pthread_mutex_lock (&mgr->priv->db_lock);
if (!exists) {
seaf_debug ("New locked file record %.8s, %s, %s, %"
G_GINT64_FORMAT".\n",
fset->repo_id, path, operation, old_mtime);
sql = "INSERT INTO LockedFiles VALUES (?, ?, ?, ?, ?, NULL)";
stmt = sqlite_query_prepare (mgr->priv->db, sql);
sqlite3_bind_text (stmt, 1, fset->repo_id, -1, SQLITE_TRANSIENT);
sqlite3_bind_text (stmt, 2, path, -1, SQLITE_TRANSIENT);
sqlite3_bind_text (stmt, 3, operation, -1, SQLITE_TRANSIENT);
sqlite3_bind_int64 (stmt, 4, old_mtime);
sqlite3_bind_text (stmt, 5, file_id, -1, SQLITE_TRANSIENT);
if (sqlite3_step (stmt) != SQLITE_DONE) {
seaf_warning ("Failed to insert locked file %s to db: %s.\n",
path, sqlite3_errmsg (mgr->priv->db));
sqlite3_finalize (stmt);
pthread_mutex_unlock (&mgr->priv->db_lock);
return -1;
}
sqlite3_finalize (stmt);
file = g_new0 (LockedFile, 1);
file->operation = g_strdup(operation);
file->old_mtime = old_mtime;
if (file_id)
memcpy (file->file_id, file_id, 40);
g_hash_table_insert (fset->locked_files, g_strdup(path), file);
} else {
seaf_debug ("Update locked file record %.8s, %s, %s.\n",
fset->repo_id, path, operation);
/* If a UPDATE record exists, don't update the old_mtime.
* We need to keep the old mtime when the locked file was first detected.
*/
sql = "UPDATE LockedFiles SET operation = ?, file_id = ? "
"WHERE repo_id = ? AND path = ?";
stmt = sqlite_query_prepare (mgr->priv->db, sql);
sqlite3_bind_text (stmt, 1, operation, -1, SQLITE_TRANSIENT);
sqlite3_bind_text (stmt, 2, file_id, -1, SQLITE_TRANSIENT);
sqlite3_bind_text (stmt, 3, fset->repo_id, -1, SQLITE_TRANSIENT);
sqlite3_bind_text (stmt, 4, path, -1, SQLITE_TRANSIENT);
if (sqlite3_step (stmt) != SQLITE_DONE) {
seaf_warning ("Failed to update locked file %s to db: %s.\n",
path, sqlite3_errmsg (mgr->priv->db));
sqlite3_finalize (stmt);
pthread_mutex_unlock (&mgr->priv->db_lock);
return -1;
}
sqlite3_finalize (stmt);
file = g_hash_table_lookup (fset->locked_files, path);
g_free (file->operation);
file->operation = g_strdup(operation);
if (file_id)
memcpy (file->file_id, file_id, 40);
}
pthread_mutex_unlock (&mgr->priv->db_lock);
return 0;
}
int
locked_file_set_remove (LockedFileSet *fset, const char *path, gboolean db_only)
{
SeafRepoManager *mgr = fset->mgr;
char *sql;
sqlite3_stmt *stmt;
if (g_hash_table_lookup (fset->locked_files, path) == NULL)
return 0;
seaf_debug ("Remove locked file record %.8s, %s.\n",
fset->repo_id, path);
pthread_mutex_lock (&mgr->priv->db_lock);
sql = "DELETE FROM LockedFiles WHERE repo_id = ? AND path = ?";
stmt = sqlite_query_prepare (mgr->priv->db, sql);
sqlite3_bind_text (stmt, 1, fset->repo_id, -1, SQLITE_TRANSIENT);
sqlite3_bind_text (stmt, 2, path, -1, SQLITE_TRANSIENT);
if (sqlite3_step (stmt) != SQLITE_DONE) {
seaf_warning ("Failed to remove locked file %s from db: %s.\n",
path, sqlite3_errmsg (mgr->priv->db));
sqlite3_finalize (stmt);
pthread_mutex_unlock (&mgr->priv->db_lock);
return -1;
}
sqlite3_finalize (stmt);
pthread_mutex_unlock (&mgr->priv->db_lock);
if (!db_only)
g_hash_table_remove (fset->locked_files, path);
return 0;
}
LockedFile *
locked_file_set_lookup (LockedFileSet *fset, const char *path)
{
return (LockedFile *) g_hash_table_lookup (fset->locked_files, path);
}
/* Folder permissions. */
FolderPerm *
folder_perm_new (const char *path, const char *permission)
{
FolderPerm *perm = g_new0 (FolderPerm, 1);
perm->path = g_strdup(path);
perm->permission = g_strdup(permission);
return perm;
}
void
folder_perm_free (FolderPerm *perm)
{
if (!perm)
return;
g_free (perm->path);
g_free (perm->permission);
g_free (perm);
}
static GList *
folder_perm_list_copy (GList *perms)
{
GList *ret = NULL, *ptr;
FolderPerm *perm, *new_perm;
for (ptr = perms; ptr; ptr = ptr->next) {
perm = ptr->data;
new_perm = folder_perm_new (perm->path, perm->permission);
ret = g_list_append (ret, new_perm);
}
return ret;
}
static gint
comp_folder_perms (gconstpointer a, gconstpointer b)
{
const FolderPerm *perm_a = a, *perm_b = b;
return (strcmp (perm_b->path, perm_a->path));
}
static void
delete_folder_perm (SeafRepoManager *mgr, const char *repo_id, FolderPermType type, FolderPerm *perm)
{
GList *folder_perms = NULL;
if (type == FOLDER_PERM_TYPE_USER) {
folder_perms = g_hash_table_lookup (mgr->priv->user_perms, repo_id);
if (!folder_perms)
return;
// if path is empty string, delete all folder perms in this repo.
if (g_strcmp0 (perm->path, "") == 0) {
g_list_free_full (folder_perms, (GDestroyNotify)folder_perm_free);
g_hash_table_remove (mgr->priv->user_perms, repo_id);
return;
}
GList *existing = g_list_find_custom (folder_perms,
perm,
comp_folder_perms);
if (existing) {
FolderPerm *old_perm = existing->data;
folder_perms = g_list_remove (folder_perms, old_perm);
g_hash_table_insert (mgr->priv->user_perms, g_strdup(repo_id), folder_perms);
folder_perm_free (old_perm);
}
} else if (type == FOLDER_PERM_TYPE_GROUP) {
folder_perms = g_hash_table_lookup (mgr->priv->group_perms, repo_id);
if (!folder_perms)
return;
if (g_strcmp0 (perm->path, "") == 0) {
g_list_free_full (folder_perms, (GDestroyNotify)folder_perm_free);
g_hash_table_remove (mgr->priv->group_perms, repo_id);
return;
}
GList *existing = g_list_find_custom (folder_perms,
perm,
comp_folder_perms);
if (existing) {
FolderPerm *old_perm = existing->data;
folder_perms = g_list_remove (folder_perms, old_perm);
g_hash_table_insert (mgr->priv->group_perms, g_strdup(repo_id), folder_perms);
folder_perm_free (old_perm);
}
}
}
int
seaf_repo_manager_delete_folder_perm (SeafRepoManager *mgr,
const char *repo_id,
FolderPermType type,
FolderPerm *perm)
{
char *sql;
sqlite3_stmt *stmt;
g_return_val_if_fail ((type == FOLDER_PERM_TYPE_USER ||
type == FOLDER_PERM_TYPE_GROUP),
-1);
if (!perm) {
return 0;
}
/* Update db. */
pthread_mutex_lock (&mgr->priv->db_lock);
if (g_strcmp0 (perm->path, "") == 0) {
if (type == FOLDER_PERM_TYPE_USER)
sql = "DELETE FROM FolderUserPerms WHERE repo_id = ?";
else
sql = "DELETE FROM FolderGroupPerms WHERE repo_id = ?";
} else {
if (type == FOLDER_PERM_TYPE_USER)
sql = "DELETE FROM FolderUserPerms WHERE repo_id = ? and path = ?";
else
sql = "DELETE FROM FolderGroupPerms WHERE repo_id = ? and path = ?";
}
stmt = sqlite_query_prepare (mgr->priv->db, sql);
sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT);
if (g_strcmp0 (perm->path, "") != 0)
sqlite3_bind_text (stmt, 2, perm->path, -1, SQLITE_TRANSIENT);
if (sqlite3_step (stmt) != SQLITE_DONE) {
seaf_warning ("Failed to remove folder perm for %.8s: %s.\n",
repo_id, sqlite3_errmsg (mgr->priv->db));
sqlite3_finalize (stmt);
pthread_mutex_unlock (&mgr->priv->db_lock);
return -1;
}
sqlite3_finalize (stmt);
pthread_mutex_unlock (&mgr->priv->db_lock);
/* Update in memory */
pthread_mutex_lock (&mgr->priv->perm_lock);
delete_folder_perm (mgr, repo_id, type, perm);
pthread_mutex_unlock (&mgr->priv->perm_lock);
return 0;
}
int
seaf_repo_manager_update_folder_perm (SeafRepoManager *mgr,
const char *repo_id,
FolderPermType type,
FolderPerm *perm)
{
char *sql;
sqlite3_stmt *stmt;
g_return_val_if_fail ((type == FOLDER_PERM_TYPE_USER ||
type == FOLDER_PERM_TYPE_GROUP),
-1);
if (!perm) {
return 0;
}
/* Update db. */
pthread_mutex_lock (&mgr->priv->db_lock);
if (type == FOLDER_PERM_TYPE_USER)
sql = "DELETE FROM FolderUserPerms WHERE repo_id = ? and path = ?";
else
sql = "DELETE FROM FolderGroupPerms WHERE repo_id = ? and path = ?";
stmt = sqlite_query_prepare (mgr->priv->db, sql);
sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT);
sqlite3_bind_text (stmt, 2, perm->path, -1, SQLITE_TRANSIENT);
if (sqlite3_step (stmt) != SQLITE_DONE) {
seaf_warning ("Failed to remove folder perm for %.8s(%s): %s.\n",
repo_id, perm->path, sqlite3_errmsg (mgr->priv->db));
sqlite3_finalize (stmt);
pthread_mutex_unlock (&mgr->priv->db_lock);
return -1;
}
sqlite3_finalize (stmt);
if (type == FOLDER_PERM_TYPE_USER)
sql = "INSERT INTO FolderUserPerms VALUES (?, ?, ?)";
else
sql = "INSERT INTO FolderGroupPerms VALUES (?, ?, ?)";
stmt = sqlite_query_prepare (mgr->priv->db, sql);
sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT);
sqlite3_bind_text (stmt, 2, perm->path, -1, SQLITE_TRANSIENT);
sqlite3_bind_text (stmt, 3, perm->permission, -1, SQLITE_TRANSIENT);
if (sqlite3_step (stmt) != SQLITE_DONE) {
seaf_warning ("Failed to insert folder perm for %.8s(%s): %s.\n",
repo_id, perm->path, sqlite3_errmsg (mgr->priv->db));
sqlite3_finalize (stmt);
pthread_mutex_unlock (&mgr->priv->db_lock);
return -1;
}
sqlite3_finalize (stmt);
pthread_mutex_unlock (&mgr->priv->db_lock);
/* Update in memory */
GList *folder_perms;
pthread_mutex_lock (&mgr->priv->perm_lock);
if (type == FOLDER_PERM_TYPE_USER) {
folder_perms = g_hash_table_lookup (mgr->priv->user_perms, repo_id);
if (folder_perms) {
GList *existing = g_list_find_custom (folder_perms,
perm,
comp_folder_perms);
if (existing) {
FolderPerm *old_perm = existing->data;
g_free (old_perm->permission);
old_perm->permission = g_strdup (perm->permission);
} else {
FolderPerm *new_perm = folder_perm_new (perm->path, perm->permission);
folder_perms = g_list_insert_sorted (folder_perms, new_perm,
comp_folder_perms);
}
} else {
FolderPerm *new_perm = folder_perm_new (perm->path, perm->permission);
folder_perms = g_list_insert_sorted (folder_perms, new_perm,
comp_folder_perms);
}
g_hash_table_insert (mgr->priv->user_perms, g_strdup(repo_id), folder_perms);
} else if (type == FOLDER_PERM_TYPE_GROUP) {
folder_perms = g_hash_table_lookup (mgr->priv->group_perms, repo_id);
if (folder_perms) {
GList *existing = g_list_find_custom (folder_perms,
perm,
comp_folder_perms);
if (existing) {
FolderPerm *old_perm = existing->data;
g_free (old_perm->permission);
old_perm->permission = g_strdup (perm->permission);
} else {
FolderPerm *new_perm = folder_perm_new (perm->path, perm->permission);
folder_perms = g_list_insert_sorted (folder_perms, new_perm,
comp_folder_perms);
}
} else {
FolderPerm *new_perm = folder_perm_new (perm->path, perm->permission);
folder_perms = g_list_insert_sorted (folder_perms, new_perm,
comp_folder_perms);
}
g_hash_table_insert (mgr->priv->group_perms, g_strdup(repo_id), folder_perms);
}
pthread_mutex_unlock (&mgr->priv->perm_lock);
return 0;
}
int
seaf_repo_manager_update_folder_perms (SeafRepoManager *mgr,
const char *repo_id,
FolderPermType type,
GList *folder_perms)
{
char *sql;
sqlite3_stmt *stmt;
GList *ptr;
FolderPerm *perm;
GList *new, *old;
g_return_val_if_fail ((type == FOLDER_PERM_TYPE_USER ||
type == FOLDER_PERM_TYPE_GROUP),
-1);
/* Update db. */
pthread_mutex_lock (&mgr->priv->db_lock);
if (type == FOLDER_PERM_TYPE_USER)
sql = "DELETE FROM FolderUserPerms WHERE repo_id = ?";
else
sql = "DELETE FROM FolderGroupPerms WHERE repo_id = ?";
stmt = sqlite_query_prepare (mgr->priv->db, sql);
sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT);
if (sqlite3_step (stmt) != SQLITE_DONE) {
seaf_warning ("Failed to remove folder perms for %.8s: %s.\n",
repo_id, sqlite3_errmsg (mgr->priv->db));
sqlite3_finalize (stmt);
pthread_mutex_unlock (&mgr->priv->db_lock);
return -1;
}
sqlite3_finalize (stmt);
if (!folder_perms) {
pthread_mutex_unlock (&mgr->priv->db_lock);
pthread_mutex_lock (&mgr->priv->perm_lock);
if (type == FOLDER_PERM_TYPE_USER) {
old = g_hash_table_lookup (mgr->priv->user_perms, repo_id);
if (old) {
g_list_free_full (old, (GDestroyNotify)folder_perm_free);
}
g_hash_table_insert (mgr->priv->user_perms, g_strdup(repo_id), NULL);
} else if (type == FOLDER_PERM_TYPE_GROUP) {
old = g_hash_table_lookup (mgr->priv->group_perms, repo_id);
if (old) {
g_list_free_full (old, (GDestroyNotify)folder_perm_free);
}
g_hash_table_insert (mgr->priv->group_perms, g_strdup(repo_id), NULL);
}
pthread_mutex_unlock (&mgr->priv->perm_lock);
return 0;
}
if (type == FOLDER_PERM_TYPE_USER)
sql = "INSERT INTO FolderUserPerms VALUES (?, ?, ?)";
else
sql = "INSERT INTO FolderGroupPerms VALUES (?, ?, ?)";
stmt = sqlite_query_prepare (mgr->priv->db, sql);
for (ptr = folder_perms; ptr; ptr = ptr->next) {
perm = ptr->data;
sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT);
sqlite3_bind_text (stmt, 2, perm->path, -1, SQLITE_TRANSIENT);
sqlite3_bind_text (stmt, 3, perm->permission, -1, SQLITE_TRANSIENT);
if (sqlite3_step (stmt) != SQLITE_DONE) {
seaf_warning ("Failed to insert folder perms for %.8s: %s.\n",
repo_id, sqlite3_errmsg (mgr->priv->db));
sqlite3_finalize (stmt);
pthread_mutex_unlock (&mgr->priv->db_lock);
return -1;
}
sqlite3_reset (stmt);
sqlite3_clear_bindings (stmt);
}
sqlite3_finalize (stmt);
pthread_mutex_unlock (&mgr->priv->db_lock);
/* Update in memory */
new = folder_perm_list_copy (folder_perms);
new = g_list_sort (new, comp_folder_perms);
pthread_mutex_lock (&mgr->priv->perm_lock);
if (type == FOLDER_PERM_TYPE_USER) {
old = g_hash_table_lookup (mgr->priv->user_perms, repo_id);
if (old)
g_list_free_full (old, (GDestroyNotify)folder_perm_free);
g_hash_table_insert (mgr->priv->user_perms, g_strdup(repo_id), new);
} else if (type == FOLDER_PERM_TYPE_GROUP) {
old = g_hash_table_lookup (mgr->priv->group_perms, repo_id);
if (old)
g_list_free_full (old, (GDestroyNotify)folder_perm_free);
g_hash_table_insert (mgr->priv->group_perms, g_strdup(repo_id), new);
}
pthread_mutex_unlock (&mgr->priv->perm_lock);
return 0;
}
static gboolean
load_folder_perm (sqlite3_stmt *stmt, void *data)
{
GList **p_perms = data;
const char *path, *permission;
path = (const char *)sqlite3_column_text (stmt, 0);
permission = (const char *)sqlite3_column_text (stmt, 1);
FolderPerm *perm = folder_perm_new (path, permission);
*p_perms = g_list_prepend (*p_perms, perm);
return TRUE;
}
static GList *
load_folder_perms_for_repo (SeafRepoManager *mgr,
const char *repo_id,
FolderPermType type)
{
GList *perms = NULL;
char sql[256];
g_return_val_if_fail ((type == FOLDER_PERM_TYPE_USER ||
type == FOLDER_PERM_TYPE_GROUP),
NULL);
if (type == FOLDER_PERM_TYPE_USER)
sqlite3_snprintf (sizeof(sql), sql,
"SELECT path, permission FROM FolderUserPerms "
"WHERE repo_id = '%q'",
repo_id);
else
sqlite3_snprintf (sizeof(sql), sql,
"SELECT path, permission FROM FolderGroupPerms "
"WHERE repo_id = '%q'",
repo_id);
pthread_mutex_lock (&mgr->priv->db_lock);
if (sqlite_foreach_selected_row (mgr->priv->db, sql,
load_folder_perm, &perms) < 0) {
pthread_mutex_unlock (&mgr->priv->db_lock);
GList *ptr;
for (ptr = perms; ptr; ptr = ptr->next)
folder_perm_free ((FolderPerm *)ptr->data);
g_list_free (perms);
return NULL;
}
pthread_mutex_unlock (&mgr->priv->db_lock);
/* Sort list in descending order by perm->path (longer path first). */
perms = g_list_sort (perms, comp_folder_perms);
return perms;
}
static void
init_folder_perms (SeafRepoManager *mgr)
{
SeafRepoManagerPriv *priv = mgr->priv;
GList *repo_ids = g_hash_table_get_keys (priv->repo_hash);
GList *ptr;
GList *perms;
char *repo_id;
for (ptr = repo_ids; ptr; ptr = ptr->next) {
repo_id = ptr->data;
perms = load_folder_perms_for_repo (mgr, repo_id, FOLDER_PERM_TYPE_USER);
if (perms) {
pthread_mutex_lock (&priv->perm_lock);
g_hash_table_insert (priv->user_perms, g_strdup(repo_id), perms);
pthread_mutex_unlock (&priv->perm_lock);
}
perms = load_folder_perms_for_repo (mgr, repo_id, FOLDER_PERM_TYPE_GROUP);
if (perms) {
pthread_mutex_lock (&priv->perm_lock);
g_hash_table_insert (priv->group_perms, g_strdup(repo_id), perms);
pthread_mutex_unlock (&priv->perm_lock);
}
}
g_list_free (repo_ids);
}
static void
remove_folder_perms (SeafRepoManager *mgr, const char *repo_id)
{
GList *perms = NULL;
pthread_mutex_lock (&mgr->priv->perm_lock);
perms = g_hash_table_lookup (mgr->priv->user_perms, repo_id);
if (perms) {
g_list_free_full (perms, (GDestroyNotify)folder_perm_free);
g_hash_table_remove (mgr->priv->user_perms, repo_id);
}
perms = g_hash_table_lookup (mgr->priv->group_perms, repo_id);
if (perms) {
g_list_free_full (perms, (GDestroyNotify)folder_perm_free);
g_hash_table_remove (mgr->priv->group_perms, repo_id);
}
pthread_mutex_unlock (&mgr->priv->perm_lock);
}
int
seaf_repo_manager_update_folder_perm_timestamp (SeafRepoManager *mgr,
const char *repo_id,
gint64 timestamp)
{
char sql[256];
int ret;
snprintf (sql, sizeof(sql),
"REPLACE INTO FolderPermTimestamp VALUES ('%s', %"G_GINT64_FORMAT")",
repo_id, timestamp);
pthread_mutex_lock (&mgr->priv->db_lock);
ret = sqlite_query_exec (mgr->priv->db, sql);
pthread_mutex_unlock (&mgr->priv->db_lock);
return ret;
}
gint64
seaf_repo_manager_get_folder_perm_timestamp (SeafRepoManager *mgr,
const char *repo_id)
{
char sql[256];
gint64 ret;
sqlite3_snprintf (sizeof(sql), sql,
"SELECT timestamp FROM FolderPermTimestamp WHERE repo_id = '%q'",
repo_id);
pthread_mutex_lock (&mgr->priv->db_lock);
ret = sqlite_get_int64 (mgr->priv->db, sql);
pthread_mutex_unlock (&mgr->priv->db_lock);
return ret;
}
static char *
lookup_folder_perm (GList *perms, const char *path)
{
GList *ptr;
FolderPerm *perm;
char *folder;
int len;
char *permission = NULL;
for (ptr = perms; ptr; ptr = ptr->next) {
perm = ptr->data;
if (strcmp (perm->path, "/") != 0)
folder = g_strconcat (perm->path, "/", NULL);
else
folder = g_strdup(perm->path);
len = strlen(folder);
if (strcmp (perm->path, path) == 0 || strncmp(folder, path, len) == 0) {
permission = perm->permission;
g_free (folder);
break;
}
g_free (folder);
}
return permission;
}
char *
get_folder_perm_by_path (const char *repo_id, const char *path)
{
SeafRepoManager *mgr = seaf->repo_mgr;
GList *user_perms = NULL, *group_perms = NULL;
char *permission = NULL;
char *abs_path = NULL;
pthread_mutex_lock (&mgr->priv->perm_lock);
user_perms = g_hash_table_lookup (mgr->priv->user_perms, repo_id);
group_perms = g_hash_table_lookup (mgr->priv->group_perms, repo_id);
if (user_perms || group_perms) {
if (path[0] != '/') {
abs_path = g_strconcat ("/", path, NULL);
} else {
abs_path = g_strdup (path);
}
}
if (user_perms)
permission = lookup_folder_perm (user_perms, abs_path);
if (!permission && group_perms)
permission = lookup_folder_perm (group_perms, abs_path);
pthread_mutex_unlock (&mgr->priv->perm_lock);
g_free (abs_path);
return permission;
}
static gboolean
is_path_writable (const char *repo_id,
gboolean is_repo_readonly,
const char *path)
{
char *permission = NULL;
permission = get_folder_perm_by_path (repo_id, path);
if (!permission)
return !is_repo_readonly;
if (strcmp (permission, "rw") == 0)
return TRUE;
else
return FALSE;
}
gboolean
seaf_repo_manager_is_path_writable (SeafRepoManager *mgr,
const char *repo_id,
const char *path)
{
SeafRepo *repo;
gboolean ret;
repo = seaf_repo_manager_get_repo (mgr, repo_id);
if (!repo) {
return FALSE;
}
ret = is_path_writable (repo_id, repo->is_readonly, path);
seaf_repo_unref (repo);
return ret;
}
// When checking whether a path is visible, we need to consider whether the parent folder is visible or not.
// If the parent folder is not visible, then even if the subfolder has visible permissions, the subfolder is not visible.
gboolean
is_path_invisible_recursive (const char *repo_id, const char *path)
{
char *permission = NULL;
if (!path || g_strcmp0 (path, ".") == 0) {
return FALSE;
}
permission = get_folder_perm_by_path (repo_id, path);
// Check if path is / after get folder perm.
if (g_strcmp0 (path, "/") == 0) {
if (!permission) {
return FALSE;
}
if (strcmp (permission, "rw") == 0 ||
strcmp (permission, "r") == 0) {
return FALSE;
}
return TRUE;
}
if (!permission) {
char *folder = g_path_get_dirname (path);
gboolean is_invisible = is_path_invisible_recursive(repo_id, folder);
g_free (folder);
return is_invisible;
}
if (strcmp (permission, "rw") == 0 ||
strcmp (permission, "r") == 0) {
char *folder = g_path_get_dirname (path);
gboolean is_invisible = is_path_invisible_recursive(repo_id, folder);
g_free (folder);
return is_invisible;
} else {
return TRUE;
}
}
gboolean
seaf_repo_manager_is_path_invisible (SeafRepoManager *mgr,
const char *repo_id,
const char *path)
{
SeafRepo *repo;
gboolean ret;
repo = seaf_repo_manager_get_repo (mgr, repo_id);
if (!repo) {
return TRUE;
}
ret = is_path_invisible_recursive (repo_id, path);
seaf_repo_unref (repo);
return ret;
}
static gboolean
include_invisible_perm (GList *perms)
{
GList *ptr;
FolderPerm *perm;
for (ptr = perms; ptr; ptr = ptr->next) {
perm = ptr->data;
if (strcmp (perm->permission, "rw") != 0 &&
strcmp (perm->permission, "r") != 0)
return TRUE;
}
return FALSE;
}
gboolean
seaf_repo_manager_include_invisible_perm (SeafRepoManager *mgr, const char *repo_id)
{
GList *user_perms = NULL, *group_perms = NULL;
pthread_mutex_lock (&mgr->priv->perm_lock);
user_perms = g_hash_table_lookup (mgr->priv->user_perms, repo_id);
if (user_perms && include_invisible_perm (user_perms)) {
pthread_mutex_unlock (&mgr->priv->perm_lock);
return TRUE;
}
group_perms = g_hash_table_lookup (mgr->priv->group_perms, repo_id);
if (group_perms && include_invisible_perm (group_perms)) {
pthread_mutex_unlock (&mgr->priv->perm_lock);
return TRUE;
}
pthread_mutex_unlock (&mgr->priv->perm_lock);
return FALSE;
}
gboolean
is_repo_id_valid (const char *id)
{
if (!id)
return FALSE;
return is_uuid_valid (id);
}
/*
* Repo object life cycle management:
*
* Each repo object has a refcnt. A repo object is inserted into an internal
* hash table in repo manager when it's created or loaded from db on startup.
* The initial refcnt is 1, for the reference by the internal hash table.
*
* A repo object can only be obtained from outside by calling
* seaf_repo_manager_get_repo(), which increase refcnt by 1.
* Once the caller is done with the object, it must call seaf_repo_unref() to
* decrease refcnt.
*
* When a repo needs to be deleted, call seaf_repo_manager_mark_repo_deleted().
* The function sets the "delete_pending" flag of the repo object.
* Repo manager won't return the repo to callers once it's marked as deleted.
* Repo manager regularly checks every repo in the hash table.
* If a repo is marked as delete pending and refcnt is 1, it removes the repo
* on disk. After the repo is removed on disk, repo manager removes the repo
* object from internal hash table.
*
* Sync manager must make sure a repo won't be downloaded when it's marked as
* delete pending. Otherwise repo manager may remove the downloaded metadata
* as it cleans up the old repo on disk.
*/
SeafRepo*
seaf_repo_new (const char *id, const char *name, const char *desc)
{
SeafRepo* repo;
repo = g_new0 (SeafRepo, 1);
memcpy (repo->id, id, 36);
repo->id[36] = '\0';
repo->name = g_strdup(name);
repo->desc = g_strdup(desc);
repo->tree = repo_tree_new (id);
return repo;
}
void
seaf_repo_free (SeafRepo *repo)
{
if (repo->head) seaf_branch_unref (repo->head);
repo_tree_free (repo->tree);
if (repo->journal)
journal_close (repo->journal, TRUE);
g_free (repo->name);
g_free (repo->desc);
g_free (repo->category);
g_free (repo->token);
g_free (repo->jwt_token);
if (repo->repo_uname)
g_free (repo->repo_uname);
if (repo->worktree)
g_free (repo->worktree);
g_free (repo->server);
g_free (repo->user);
g_free (repo->fileserver_addr);
g_free (repo->pwd_hash_algo);
g_free (repo->pwd_hash_params);
g_free (repo);
}
void
seaf_repo_ref (SeafRepo *repo)
{
g_atomic_int_inc (&repo->refcnt);
}
void
seaf_repo_unref (SeafRepo *repo)
{
if (!repo)
return;
pthread_rwlock_wrlock (&seaf->repo_mgr->priv->lock);
if (g_atomic_int_dec_and_test (&repo->refcnt))
seaf_repo_free (repo);
pthread_rwlock_unlock (&seaf->repo_mgr->priv->lock);
}
static void
set_head_common (SeafRepo *repo, SeafBranch *branch)
{
if (repo->head)
seaf_branch_unref (repo->head);
repo->head = branch;
seaf_branch_ref(branch);
}
int
seaf_repo_set_head (SeafRepo *repo, SeafBranch *branch)
{
if (save_branch_repo_map (seaf->repo_mgr, branch) < 0)
return -1;
set_head_common (repo, branch);
return 0;
}
SeafCommit *
seaf_repo_get_head_commit (const char *repo_id)
{
SeafRepo *repo = NULL;
SeafCommit *head = NULL;
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo) {
seaf_warning ("Failed to get repo %s.\n", repo_id);
return NULL;
}
head = seaf_commit_manager_get_commit (seaf->commit_mgr,
repo_id, repo->version,
repo->head->commit_id);
if (!head) {
seaf_warning ("Failed to get head for repo %s.\n", repo_id);
}
seaf_repo_unref (repo);
return head;
}
void
seaf_repo_from_commit (SeafRepo *repo, SeafCommit *commit)
{
repo->name = g_strdup (commit->repo_name);
repo->desc = g_strdup (commit->repo_desc);
repo->encrypted = commit->encrypted;
repo->last_modify = commit->ctime;
memcpy (repo->root_id, commit->root_id, 40);
if (repo->encrypted) {
repo->enc_version = commit->enc_version;
if (repo->enc_version == 1 && !commit->pwd_hash_algo)
memcpy (repo->magic, commit->magic, 32);
else if (repo->enc_version == 2) {
memcpy (repo->random_key, commit->random_key, 96);
}
else if (repo->enc_version == 3) {
memcpy (repo->salt, commit->salt, 64);
memcpy (repo->random_key, commit->random_key, 96);
}
else if (repo->enc_version == 4) {
memcpy (repo->salt, commit->salt, 64);
memcpy (repo->random_key, commit->random_key, 96);
}
if (repo->enc_version >= 2 && !commit->pwd_hash_algo) {
memcpy (repo->magic, commit->magic, 64);
}
if (commit->pwd_hash_algo) {
memcpy (repo->pwd_hash, commit->pwd_hash, 64);
repo->pwd_hash_algo = g_strdup (commit->pwd_hash_algo);
repo->pwd_hash_params = g_strdup (commit->pwd_hash_params);
}
}
repo->version = commit->version;
}
void
seaf_repo_to_commit (SeafRepo *repo, SeafCommit *commit)
{
commit->repo_name = g_strdup (repo->name);
commit->repo_desc = g_strdup (repo->desc);
commit->encrypted = repo->encrypted;
if (commit->encrypted) {
commit->enc_version = repo->enc_version;
if (commit->enc_version == 1 && !repo->pwd_hash_algo)
commit->magic = g_strdup (repo->magic);
else if (commit->enc_version == 2) {
commit->random_key = g_strdup (repo->random_key);
}
else if (commit->enc_version == 3) {
commit->salt = g_strdup (repo->salt);
commit->random_key = g_strdup (repo->random_key);
}
else if (commit->enc_version == 4) {
commit->salt = g_strdup (repo->salt);
commit->random_key = g_strdup (repo->random_key);
}
if (commit->enc_version >= 2 && !repo->pwd_hash_algo) {
commit->magic = g_strdup (repo->magic);
}
if (repo->pwd_hash_algo) {
commit->pwd_hash = g_strdup (repo->pwd_hash);
commit->pwd_hash_algo = g_strdup (repo->pwd_hash_algo);
commit->pwd_hash_params = g_strdup (repo->pwd_hash_params);
}
}
commit->version = repo->version;
}
void
seaf_repo_set_readonly (SeafRepo *repo)
{
repo->is_readonly = TRUE;
save_repo_property (repo->manager, repo->id, REPO_PROP_IS_READONLY, "true");
}
void
seaf_repo_unset_readonly (SeafRepo *repo)
{
repo->is_readonly = FALSE;
save_repo_property (repo->manager, repo->id, REPO_PROP_IS_READONLY, "false");
}
void
seaf_repo_set_worktree (SeafRepo *repo, const char *repo_uname)
{
if (repo->repo_uname)
g_free (repo->repo_uname);
repo->repo_uname = g_strdup (repo_uname);
if (repo->worktree)
g_free (repo->worktree);
repo->worktree = g_build_path ("/", seaf->mount_point, repo_uname, NULL);
}
static void
print_apply_op_error (const char *repo_id, JournalOp *op, int err)
{
seaf_warning ("Failed to apply op to repo tree of %s: %s.\n"
"Op details: %d %s %s %"G_GINT64_FORMAT" %"G_GINT64_FORMAT" %u\n",
repo_id, strerror(err),
op->type, op->path, op->new_path, op->size, op->mtime, op->mode);
}
static int
apply_journal_ops_to_repo_tree (SeafRepo *repo)
{
GList *ops, *ptr;
gboolean error;
JournalOp *op;
RepoTree *tree = repo->tree;
int rc;
int ret = 0;
ops = journal_read_ops (repo->journal, repo->head->opid + 1, G_MAXINT64, &error);
if (error) {
seaf_warning ("Failed to read operations from journal for repo %s.\n",
repo->id);
return -1;
}
for (ptr = ops; ptr; ptr = ptr->next) {
op = (JournalOp *)ptr->data;
switch (op->type) {
case OP_TYPE_CREATE_FILE:
rc = repo_tree_create_file (tree, op->path, EMPTY_SHA1, op->mode, op->mtime, op->size);
if (rc < 0) {
print_apply_op_error (repo->id, op, -rc);
ret = -1;
goto out;
}
break;
case OP_TYPE_DELETE_FILE:
rc = repo_tree_unlink (tree, op->path);
if (rc < 0) {
print_apply_op_error (repo->id, op, -rc);
ret = -1;
goto out;
}
break;
case OP_TYPE_UPDATE_FILE:
rc = repo_tree_set_file_mtime (tree, op->path, op->mtime);
if (rc < 0) {
print_apply_op_error (repo->id, op, -rc);
ret = -1;
goto out;
}
rc = repo_tree_set_file_size (tree, op->path, op->size);
if (rc < 0) {
print_apply_op_error (repo->id, op, -rc);
ret = -1;
goto out;
}
break;
case OP_TYPE_RENAME:
rc = repo_tree_rename (tree, op->path, op->new_path, TRUE);
if (rc < 0) {
print_apply_op_error (repo->id, op, -rc);
ret = -1;
goto out;
}
break;
case OP_TYPE_MKDIR:
rc = repo_tree_mkdir (tree, op->path, op->mtime);
if (rc < 0) {
print_apply_op_error (repo->id, op, -rc);
ret = -1;
goto out;
}
break;
case OP_TYPE_RMDIR:
rc = repo_tree_rmdir (tree, op->path);
if (rc < 0) {
print_apply_op_error (repo->id, op, -rc);
ret = -1;
goto out;
}
break;
case OP_TYPE_UPDATE_ATTR:
rc = repo_tree_set_file_mtime (tree, op->path, op->mtime);
if (rc < 0) {
print_apply_op_error (repo->id, op, -rc);
ret = -1;
goto out;
}
break;
default:
seaf_warning ("Unknown op type %d, skipped.\n", op->type);
}
}
out:
g_list_free_full (ops, (GDestroyNotify)journal_op_free);
return ret;
}
static char *
build_conflict_path (const char *user, const char *path, time_t t)
{
char time_buf[64];
char *copy = g_strdup (path);
GString *conflict_path = g_string_new (NULL);
char *dot, *ext;
strftime(time_buf, 64, "%Y-%m-%d-%H-%M-%S", localtime(&t));
dot = strrchr (copy, '.');
if (dot != NULL) {
*dot = '\0';
ext = dot + 1;
g_string_printf (conflict_path, "%s (SFConflict %s %s).%s",
copy, user, time_buf, ext);
} else {
g_string_printf (conflict_path, "%s (SFConflict %s %s)",
copy, user, time_buf);
}
g_free (copy);
return g_string_free (conflict_path, FALSE);
}
static int
copy_file_to_conflict_file (SeafRepo *repo,
SeafStat *st,
const char *path,
const char *conflict_path)
{
JournalOp *op;
int ret = 0;
repo_tree_create_file (repo->tree, conflict_path,
EMPTY_SHA1, st->st_mode, st->st_mtime, st->st_size);
op = journal_op_new (OP_TYPE_CREATE_FILE, conflict_path, NULL, 0, 0, st->st_mode);
if (journal_append_op (repo->journal, op) < 0) {
journal_op_free (op);
ret = -1;
goto out;
}
op = journal_op_new (OP_TYPE_UPDATE_FILE, conflict_path, NULL,
st->st_size, st->st_mtime, st->st_mode);
if (journal_append_op (repo->journal, op) < 0) {
journal_op_free (op);
ret = -1;
goto out;
}
file_cache_mgr_rename (seaf->file_cache_mgr, repo->id, path, repo->id, conflict_path);
/* Reset extended attrs so that the file will be indexed on commit. */
file_cache_mgr_set_attrs (seaf->file_cache_mgr, repo->id, conflict_path,
0, 0, EMPTY_SHA1);
out:
return ret;
}
static int
create_parent_dirs (SeafRepo *repo, const char *parent_path)
{
char **parts = g_strsplit (parent_path, "/", 0);
guint len = g_strv_length (parts);
guint i;
GString *dir_path = NULL;
int rc;
RepoTreeStat tree_st;
JournalOp *op;
int ret = 0;
if (len == 0)
return 0;
dir_path = g_string_new ("");
for (i = 0; i < len; ++i) {
if (i == 0)
g_string_append (dir_path, parts[0]);
else
g_string_append_printf (dir_path, "/%s", parts[i]);
if (repo_tree_stat_path (repo->tree, dir_path->str, &tree_st) == 0) {
continue;
}
rc = repo_tree_mkdir (repo->tree, dir_path->str, (gint64)time(NULL));
if (rc < 0) {
seaf_warning ("Failed to create dir %s/%s in repo tree: %s.\n",
repo->id, dir_path->str, strerror(rc));
ret = -1;
break;
}
op = journal_op_new (OP_TYPE_MKDIR, dir_path->str, NULL, 0, (gint64)time(NULL), 0);
if (journal_append_op (repo->journal, op) < 0) {
seaf_warning ("Failed to append operation to journal of repo %s.\n",
repo->id);
journal_op_free (op);
ret = -1;
break;
}
}
g_string_free (dir_path, TRUE);
g_strfreev (parts);
return ret;
}
static int
add_cached_file_to_tree (SeafRepo *repo, SeafStat *st, const char *path)
{
JournalOp *op;
char *parent_path;
parent_path = g_path_get_dirname (path);
if (g_strcmp0 (parent_path, ".") != 0) {
if (create_parent_dirs (repo, parent_path) < 0) {
g_free (parent_path);
return -1;
}
}
g_free (parent_path);
repo_tree_create_file (repo->tree, path,
EMPTY_SHA1, st->st_mode, st->st_mtime, st->st_size);
op = journal_op_new (OP_TYPE_CREATE_FILE, path, NULL, 0, 0, st->st_mode);
if (journal_append_op (repo->journal, op) < 0) {
journal_op_free (op);
return -1;
}
op = journal_op_new (OP_TYPE_UPDATE_FILE, path, NULL,
st->st_size, st->st_mtime, st->st_mode);
if (journal_append_op (repo->journal, op) < 0) {
journal_op_free (op);
return -1;
}
/* Reset extended attrs so that the file will be indexed on commit. */
file_cache_mgr_set_attrs (seaf->file_cache_mgr, repo->id, path,
0, 0, EMPTY_SHA1);
return 0;
}
static int
update_cached_file_in_tree (SeafRepo *repo, SeafStat *st, const char *path)
{
JournalOp *op;
repo_tree_set_file_mtime (repo->tree, path, (gint64)st->st_mtime);
repo_tree_set_file_size (repo->tree, path, (gint64)st->st_size);
op = journal_op_new (OP_TYPE_UPDATE_FILE, path, NULL,
st->st_size, st->st_mtime, st->st_mode);
if (journal_append_op (repo->journal, op) < 0) {
journal_op_free (op);
return -1;
}
return 0;
}
typedef struct TraverseCachedFileAux {
SeafRepo *repo;
gboolean after_clone;
char *nickname;
} TraverseCachedFileAux;
static void
check_cached_file_status_cb (const char *repo_id,
const char *file_path,
SeafStat *st,
void *user_data)
{
TraverseCachedFileAux *aux = (TraverseCachedFileAux *)user_data;
SeafRepo *repo = aux->repo;
RepoTreeStat tree_st;
char *filename;
FileCacheStat attrs;
/* If file is not completely cached yet, don't rely on its status. */
if (!file_cache_mgr_is_file_cached (seaf->file_cache_mgr,
repo_id, file_path))
return;
if (file_cache_mgr_get_attrs (seaf->file_cache_mgr, repo_id, file_path, &attrs) < 0) {
seaf_warning ("Failed to get cached attrs for file %s/%s\n",
repo_id, file_path);
return;
}
filename = g_path_get_basename (file_path);
if (repo_tree_stat_path (repo->tree, file_path, &tree_st) < 0) {
if (seaf_repo_manager_ignored_on_commit(filename)) {
/* Internally ignored files are not committed to the tree.
* So after restart, these files are not loaded.
* We can pick them up in the cache files folder.
*/
repo_tree_create_file (repo->tree, file_path,
attrs.file_id,
(guint32)st->st_mode,
(gint64)st->st_mtime,
(gint64)st->st_size);
} else if (aux->after_clone ||
attrs.mtime != (gint64)st->st_mtime ||
attrs.size != (gint64)st->st_size) {
/* If the program was hard shutdown, some operations were not flushed
* into journal db. In this case, there could be some files missing from
* journal and repo tree while present in the file cache.
*/
seaf_message ("Adding cached file %s/%s to tree.\n", repo->id, file_path);
add_cached_file_to_tree (repo, st, file_path);
}
g_free (filename);
return;
}
if (aux->after_clone) {
/* Merge existing but not-yet committed cached files into repo after clone. */
if (((gint64)st->st_mtime != tree_st.mtime || (gint64)st->st_size != tree_st.size) &&
((gint64)st->st_mtime != attrs.mtime || (gint64)st->st_size != attrs.size || !attrs.is_uploaded) &&
!seaf_repo_manager_ignored_on_commit(filename)) {
seaf_message ("Cached file %s of repo %s(%.8s) is changed and different from repo tree status.\n"
"Cached file: mtime %"G_GINT64_FORMAT", size %"G_GINT64_FORMAT"\n"
"Extended attrs: mtime: %"G_GINT64_FORMAT", size %"G_GINT64_FORMAT"\n"
"Repo tree: mtime %"G_GINT64_FORMAT", size %"G_GINT64_FORMAT"\n",
file_path, repo->name, repo->id,
(gint64)st->st_mtime, (gint64)st->st_size,
attrs.mtime, attrs.size,
tree_st.mtime, tree_st.size);
char *conflict_path = build_conflict_path (aux->nickname, file_path, st->st_mtime);
if (conflict_path) {
seaf_message ("Generating conflict file %s in repo %s.\n",
conflict_path, repo_id);
copy_file_to_conflict_file (repo, st, file_path, conflict_path);
g_free (conflict_path);
}
}
} else {
/* On start-up, if a cached file is not committed and its timestamp or size is
* different from the repo tree, some updates to it was not flushed into journal
* on the last shutdown.
*/
if (((gint64)st->st_mtime != tree_st.mtime || (gint64)st->st_size != tree_st.size) &&
((gint64)st->st_mtime != attrs.mtime || (gint64)st->st_size != attrs.size) &&
!seaf_repo_manager_ignored_on_commit(filename)) {
seaf_message ("Updating cached file %s/%s in tree.\n", repo->id, file_path);
seaf_message ("Cached file: mtime %"G_GINT64_FORMAT", size %"G_GINT64_FORMAT"\n"
"Extended attrs: mtime: %"G_GINT64_FORMAT", size %"G_GINT64_FORMAT"\n"
"Repo tree: mtime: %"G_GINT64_FORMAT", size %"G_GINT64_FORMAT"\n",
(gint64)st->st_mtime, (gint64)st->st_size,
attrs.mtime, attrs.size,
tree_st.mtime, tree_st.size);
update_cached_file_in_tree (repo, st, file_path);
}
}
g_free (filename);
if ((gint64)st->st_mtime == attrs.mtime && (gint64)st->st_size == attrs.size) {
seaf_sync_manager_update_active_path (seaf->sync_mgr, repo_id, file_path,
st->st_mode, SYNC_STATUS_SYNCED);
}
}
/* static void */
/* check_cached_dir_cb (const char *repo_id, */
/* const char *dir_path, */
/* SeafStat *st, */
/* void *user_data) */
/* { */
/* TraverseCachedFileAux *aux = (TraverseCachedFileAux *)user_data; */
/* SeafRepo *repo = aux->repo; */
/* RepoTreeStat tree_st; */
/* JournalOp *op; */
/* if (repo_tree_stat_path (repo->tree, dir_path, &tree_st) == 0) { */
/* return; */
/* } */
/* int rc = repo_tree_mkdir (repo->tree, dir_path, (gint64)st->st_mtime); */
/* if (rc < 0) { */
/* return; */
/* } */
/* op = journal_op_new (OP_TYPE_MKDIR, dir_path, NULL, 0, (gint64)st->st_mtime, 0); */
/* if (journal_append_op (repo->journal, op) < 0) { */
/* seaf_warning ("Failed to append operation to journal of repo %s.\n", */
/* repo_id); */
/* journal_op_free (op); */
/* } */
/* } */
int
seaf_repo_load_fs (SeafRepo *repo, gboolean after_clone)
{
if (repo->fs_ready)
return 0;
if (!repo->journal) {
repo->journal = journal_manager_open_journal (seaf->journal_mgr,
repo->id);
if (!repo->journal) {
seaf_warning ("Failed to open journal for repo %s(%s).\n",
repo->name, repo->id);
return -1;
}
}
if (repo_tree_load_commit (repo->tree, repo->head->commit_id) < 0) {
seaf_warning ("Failed to load tree for repo %s.\n", repo->id);
journal_close (repo->journal, TRUE);
repo->journal = NULL;
return -1;
}
/* Ignore errors when replaying the journal.
* There can sometimes be edge case that fails an operation replay, like deleting
* a non-existing file. These cases shouldn't harm.
*/
apply_journal_ops_to_repo_tree (repo);
TraverseCachedFileAux aux;
aux.repo = repo;
aux.after_clone = after_clone;
SeafAccount *account = seaf_repo_manager_get_account (seaf->repo_mgr, repo->server, repo->user);
if (account && account->nickname) {
aux.nickname = g_strdup (account->nickname);
} else {
aux.nickname = g_strdup (repo->user);
}
seaf_account_free (account);
file_cache_mgr_traverse_path (seaf->file_cache_mgr,
repo->id,
"",
check_cached_file_status_cb,
NULL,
&aux);
g_free (aux.nickname);
repo->fs_ready = TRUE;
return 0;
}
SeafRepoManager*
seaf_repo_manager_new (SeafileSession *seaf)
{
SeafRepoManager *mgr = g_new0 (SeafRepoManager, 1);
mgr->priv = g_new0 (SeafRepoManagerPriv, 1);
mgr->seaf = seaf;
pthread_mutex_init (&mgr->priv->db_lock, NULL);
mgr->priv->repo_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
pthread_rwlock_init (&mgr->priv->lock, NULL);
mgr->priv->user_perms = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
mgr->priv->group_perms = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
pthread_mutex_init (&mgr->priv->perm_lock, NULL);
mgr->priv->accounts = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free,
(GDestroyNotify)seaf_account_free);
pthread_rwlock_init (&mgr->priv->account_lock, NULL);
mgr->priv->repo_infos = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free,
(GDestroyNotify)repo_info_free);
mgr->priv->repo_names = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, (GDestroyNotify)g_hash_table_destroy);
return mgr;
}
int
seaf_repo_manager_init (SeafRepoManager *mgr)
{
/* Load all the repos into memory on the client side. */
load_repos (mgr, mgr->seaf->seaf_dir);
/* Load folder permissions from db. */
init_folder_perms (mgr);
return 0;
}
#define REMOVE_OBJECTS_BATCH 1000
static int
remove_store (const char *top_store_dir, const char *store_id, int *count)
{
char *obj_dir = NULL;
GDir *dir1, *dir2;
const char *dname1, *dname2;
char *path1, *path2;
obj_dir = g_build_filename (top_store_dir, store_id, NULL);
dir1 = g_dir_open (obj_dir, 0, NULL);
if (!dir1) {
g_free (obj_dir);
return 0;
}
seaf_message ("Removing store %s\n", obj_dir);
while ((dname1 = g_dir_read_name(dir1)) != NULL) {
path1 = g_build_filename (obj_dir, dname1, NULL);
dir2 = g_dir_open (path1, 0, NULL);
if (!dir2) {
seaf_warning ("Failed to open obj dir %s.\n", path1);
g_dir_close (dir1);
g_free (path1);
g_free (obj_dir);
return -1;
}
while ((dname2 = g_dir_read_name(dir2)) != NULL) {
path2 = g_build_filename (path1, dname2, NULL);
g_unlink (path2);
/* To prevent using too much IO, only remove 1000 objects per 5 seconds.
*/
if (++(*count) > REMOVE_OBJECTS_BATCH) {
g_usleep (5 * G_USEC_PER_SEC);
*count = 0;
}
g_free (path2);
}
g_dir_close (dir2);
g_rmdir (path1);
g_free (path1);
}
g_dir_close (dir1);
g_rmdir (obj_dir);
g_free (obj_dir);
return 0;
}
static void
cleanup_deleted_stores_by_type (const char *type)
{
char *top_store_dir;
const char *repo_id;
top_store_dir = g_build_filename (seaf->seaf_dir, "deleted_store", type, NULL);
GError *error = NULL;
GDir *dir = g_dir_open (top_store_dir, 0, &error);
if (!dir) {
seaf_warning ("Failed to open store dir %s: %s.\n",
top_store_dir, error->message);
g_free (top_store_dir);
return;
}
int count = 0;
while ((repo_id = g_dir_read_name(dir)) != NULL) {
remove_store (top_store_dir, repo_id, &count);
}
g_free (top_store_dir);
g_dir_close (dir);
}
static void *
cleanup_deleted_stores (void *vdata)
{
while (1) {
cleanup_deleted_stores_by_type ("commits");
cleanup_deleted_stores_by_type ("fs");
cleanup_deleted_stores_by_type ("blocks");
g_usleep (60 * G_USEC_PER_SEC);
}
return NULL;
}
int
del_repo (SeafRepoManager *mgr, SeafRepo *repo)
{
char *repo_id = g_strdup (repo->id);
gboolean remove_cache = repo->remove_cache;
seaf_sync_manager_remove_active_path_info (seaf->sync_mgr, repo->id);
seaf_repo_manager_remove_repo_ondisk (mgr, repo->id, remove_cache);
/* Only remove the repo object from hash table after it's removed on disk.
* Otherwise the sync manager may find that the repo doesn't exist and clone it
* while the on-disk removal is still in progress.
*/
pthread_rwlock_wrlock (&mgr->priv->lock);
g_hash_table_remove (mgr->priv->repo_hash, repo->id);
pthread_rwlock_unlock (&mgr->priv->lock);
seaf_repo_unref (repo);
//Journal database is closed when object is released.So we have to delete
//database file after repo object is release,otherwise the file is still
//opened and not possible to delete on windows.
journal_manager_delete_journal (seaf->journal_mgr, repo_id);
g_free (repo_id);
return 0;
}
static void *
cleanup_deleted_repos (void *vdata)
{
SeafRepoManager *mgr = vdata;
GHashTableIter iter;
gpointer key, value;
SeafRepo *repo;
GList *deleted = NULL;
GList *ptr;
while (1) {
pthread_rwlock_wrlock (&mgr->priv->lock);
g_hash_table_iter_init (&iter, mgr->priv->repo_hash);
while (g_hash_table_iter_next (&iter, &key, &value)) {
repo = value;
/* Delete a repo if it's marked as delete pending and
* it's only referenced by the internal hash table.
*/
if (repo->delete_pending && repo->refcnt == 1) {
deleted = g_list_prepend (deleted, repo);
}
}
pthread_rwlock_unlock (&mgr->priv->lock);
for (ptr = deleted; ptr; ptr = ptr->next) {
repo = ptr->data;
del_repo (mgr, repo);
}
g_list_free (deleted);
deleted = NULL;
g_usleep (G_USEC_PER_SEC);
}
return NULL;
}
int
seaf_repo_manager_start (SeafRepoManager *mgr)
{
pthread_t tid;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
int rc = pthread_create (&tid, &attr, cleanup_deleted_stores, NULL);
if (rc != 0) {
seaf_warning ("Failed to start cleanup thread: %s\n", strerror(rc));
return -1;
}
rc = pthread_create (&tid, &attr, cleanup_deleted_repos, mgr);
if (rc != 0) {
seaf_warning ("Failed to start cleanup deleted repo thread: %s\n", strerror(rc));
return -1;
}
return 0;
}
int
seaf_repo_manager_add_repo (SeafRepoManager *manager,
SeafRepo *repo)
{
char sql[256];
sqlite3 *db = manager->priv->db;
pthread_mutex_lock (&manager->priv->db_lock);
snprintf (sql, sizeof(sql), "REPLACE INTO Repo VALUES ('%s');", repo->id);
sqlite_query_exec (db, sql);
pthread_mutex_unlock (&manager->priv->db_lock);
repo->manager = manager;
if (pthread_rwlock_wrlock (&manager->priv->lock) < 0) {
seaf_warning ("[repo mgr] failed to lock repo cache.\n");
return -1;
}
g_hash_table_insert (manager->priv->repo_hash, g_strdup(repo->id), repo);
seaf_repo_ref (repo);
pthread_rwlock_unlock (&manager->priv->lock);
return 0;
}
int
seaf_repo_manager_mark_repo_deleted (SeafRepoManager *mgr,
SeafRepo *repo,
gboolean remove_cache)
{
char sql[256];
seaf_message ("Mark repo %s(%.8s) as deleted.\n", repo->name, repo->id);
pthread_mutex_lock (&mgr->priv->db_lock);
snprintf (sql, sizeof(sql), "INSERT INTO DeletedRepo VALUES ('%s', %d)",
repo->id, remove_cache);
if (sqlite_query_exec (mgr->priv->db, sql) < 0) {
pthread_mutex_unlock (&mgr->priv->db_lock);
return -1;
}
pthread_mutex_unlock (&mgr->priv->db_lock);
repo->delete_pending = TRUE;
repo->remove_cache = remove_cache;
#ifdef COMPILE_WS
seaf_notif_manager_unsubscribe_repo (seaf->notif_mgr, repo);
#endif
return 0;
}
static char *
gen_deleted_store_path (const char *type, const char *repo_id)
{
int n = 1;
char *path = NULL;
char *name = NULL;
path = g_build_filename (seaf->deleted_store, type, repo_id, NULL);
while (g_file_test(path, G_FILE_TEST_EXISTS) && n < 10) {
g_free (path);
name = g_strdup_printf ("%s(%d)", repo_id, n);
path = g_build_filename (seaf->deleted_store, type, name, NULL);
g_free (name);
++n;
}
if (n == 10) {
g_free (path);
return NULL;
}
return path;
}
void
seaf_repo_manager_move_repo_store (SeafRepoManager *mgr,
const char *type,
const char *repo_id)
{
char *src = NULL;
char *dst = NULL;
src = g_build_filename (seaf->seaf_dir, "storage", type, repo_id, NULL);
if (!seaf_util_exists (src)) {
return;
}
dst = gen_deleted_store_path (type, repo_id);
if (dst) {
g_rename (src, dst);
}
g_free (src);
g_free (dst);
}
/* Move commits, fs stores into "deleted_store" directory. */
static void
move_repo_stores (SeafRepoManager *mgr, const char *repo_id)
{
seaf_repo_manager_move_repo_store (mgr, "commits", repo_id);
seaf_repo_manager_move_repo_store (mgr, "fs", repo_id);
}
void
seaf_repo_manager_remove_repo_ondisk (SeafRepoManager *mgr,
const char *repo_id,
gboolean remove_cache)
{
char sql[256];
seaf_message ("Removing repo %s on disk.\n", repo_id);
pthread_mutex_lock (&mgr->priv->db_lock);
snprintf (sql, sizeof(sql), "DELETE FROM Repo WHERE repo_id = '%s'", repo_id);
if (sqlite_query_exec (mgr->priv->db, sql) < 0) {
pthread_mutex_unlock (&mgr->priv->db_lock);
return;
}
pthread_mutex_unlock (&mgr->priv->db_lock);
/* remove branch */
GList *p;
GList *branch_list =
seaf_branch_manager_get_branch_list (seaf->branch_mgr, repo_id);
for (p = branch_list; p; p = p->next) {
SeafBranch *b = (SeafBranch *)p->data;
seaf_repo_manager_branch_repo_unmap (mgr, b);
seaf_branch_manager_del_branch (seaf->branch_mgr, repo_id, b->name);
}
seaf_branch_list_free (branch_list);
/* delete repo property firstly */
seaf_repo_manager_del_repo_property (mgr, repo_id);
pthread_mutex_lock (&mgr->priv->db_lock);
snprintf (sql, sizeof(sql), "DELETE FROM RepoKeys WHERE repo_id = '%s'",
repo_id);
sqlite_query_exec (mgr->priv->db, sql);
snprintf (sql, sizeof(sql), "DELETE FROM FolderUserPerms WHERE repo_id = '%s'",
repo_id);
sqlite_query_exec (mgr->priv->db, sql);
snprintf (sql, sizeof(sql), "DELETE FROM FolderGroupPerms WHERE repo_id = '%s'",
repo_id);
sqlite_query_exec (mgr->priv->db, sql);
snprintf (sql, sizeof(sql), "DELETE FROM FolderPermTimestamp WHERE repo_id = '%s'",
repo_id);
sqlite_query_exec (mgr->priv->db, sql);
pthread_mutex_unlock (&mgr->priv->db_lock);
seaf_filelock_manager_remove (seaf->filelock_mgr, repo_id);
remove_folder_perms (mgr, repo_id);
move_repo_stores (mgr, repo_id);
if (remove_cache)
file_cache_mgr_delete_repo_cache (seaf->file_cache_mgr, repo_id);
/* At last remove the deleted record from db. If the above procedure is
* interrupted, we can still redo the operations on next start.
*/
pthread_mutex_lock (&mgr->priv->db_lock);
snprintf (sql, sizeof(sql),
"DELETE FROM DeletedRepo WHERE repo_id = '%s'", repo_id);
sqlite_query_exec (mgr->priv->db, sql);
pthread_mutex_unlock (&mgr->priv->db_lock);
}
SeafRepo*
seaf_repo_manager_get_repo (SeafRepoManager *manager, const gchar *id)
{
SeafRepo *repo, *res = NULL;
if (!id) {
return NULL;
}
if (pthread_rwlock_rdlock (&manager->priv->lock) < 0) {
seaf_warning ("[repo mgr] failed to lock repo cache.\n");
return NULL;
}
repo = g_hash_table_lookup (manager->priv->repo_hash, id);
if (repo && !repo->delete_pending) {
seaf_repo_ref (repo);
res = repo;
}
pthread_rwlock_unlock (&manager->priv->lock);
return res;
}
gboolean
seaf_repo_manager_repo_exists (SeafRepoManager *manager, const gchar *id)
{
SeafRepo *repo;
gboolean res = FALSE;
if (pthread_rwlock_rdlock (&manager->priv->lock) < 0) {
seaf_warning ("[repo mgr] failed to lock repo cache.\n");
return FALSE;
}
repo = g_hash_table_lookup (manager->priv->repo_hash, id);
if (repo && !repo->delete_pending)
res = TRUE;
pthread_rwlock_unlock (&manager->priv->lock);
return res;
}
static int
save_branch_repo_map (SeafRepoManager *manager, SeafBranch *branch)
{
char *sql;
sqlite3 *db = manager->priv->db;
pthread_mutex_lock (&manager->priv->db_lock);
sql = sqlite3_mprintf ("REPLACE INTO RepoBranch VALUES (%Q, %Q)",
branch->repo_id, branch->name);
sqlite_query_exec (db, sql);
sqlite3_free (sql);
pthread_mutex_unlock (&manager->priv->db_lock);
return 0;
}
int
seaf_repo_manager_branch_repo_unmap (SeafRepoManager *manager, SeafBranch *branch)
{
char *sql;
sqlite3 *db = manager->priv->db;
pthread_mutex_lock (&manager->priv->db_lock);
sql = sqlite3_mprintf ("DELETE FROM RepoBranch WHERE branch_name = %Q"
" AND repo_id = %Q",
branch->name, branch->repo_id);
if (sqlite_query_exec (db, sql) < 0) {
seaf_warning ("Unmap branch repo failed\n");
pthread_mutex_unlock (&manager->priv->db_lock);
sqlite3_free (sql);
return -1;
}
sqlite3_free (sql);
pthread_mutex_unlock (&manager->priv->db_lock);
return 0;
}
static void
load_repo_commit (SeafRepoManager *manager,
SeafRepo *repo,
SeafBranch *branch)
{
SeafCommit *commit;
commit = seaf_commit_manager_get_commit_compatible (manager->seaf->commit_mgr,
repo->id,
branch->commit_id);
if (!commit) {
seaf_warning ("Commit %s/%s is missing\n",
repo->id, branch->commit_id);
repo->is_corrupted = TRUE;
return;
}
set_head_common (repo, branch);
seaf_repo_from_commit (repo, commit);
seaf_commit_unref (commit);
}
static gboolean
load_keys_cb (sqlite3_stmt *stmt, void *vrepo)
{
SeafRepo *repo = vrepo;
const char *key, *iv;
key = (const char *)sqlite3_column_text(stmt, 0);
iv = (const char *)sqlite3_column_text(stmt, 1);
if (repo->enc_version == 1) {
hex_to_rawdata (key, repo->enc_key, 16);
hex_to_rawdata (iv, repo->enc_iv, 16);
} else if (repo->enc_version >= 2) {
hex_to_rawdata (key, repo->enc_key, 32);
hex_to_rawdata (iv, repo->enc_iv, 16);
}
repo->is_passwd_set = TRUE;
return FALSE;
}
static int
load_repo_passwd (SeafRepoManager *manager, SeafRepo *repo)
{
sqlite3 *db = manager->priv->db;
char sql[256];
int n;
pthread_mutex_lock (&manager->priv->db_lock);
snprintf (sql, sizeof(sql),
"SELECT key, iv FROM RepoKeys WHERE repo_id='%s'",
repo->id);
n = sqlite_foreach_selected_row (db, sql, load_keys_cb, repo);
if (n < 0) {
pthread_mutex_unlock (&manager->priv->db_lock);
return -1;
}
pthread_mutex_unlock (&manager->priv->db_lock);
return 0;
}
static gboolean
load_property_cb (sqlite3_stmt *stmt, void *pvalue)
{
char **value = pvalue;
char *v = (char *) sqlite3_column_text (stmt, 0);
*value = g_strdup (v);
/* Only one result. */
return FALSE;
}
static char *
load_repo_property (SeafRepoManager *manager,
const char *repo_id,
const char *key)
{
sqlite3 *db = manager->priv->db;
char sql[256];
char *value = NULL;
pthread_mutex_lock (&manager->priv->db_lock);
snprintf(sql, 256, "SELECT value FROM RepoProperty WHERE "
"repo_id='%s' and key='%s'", repo_id, key);
if (sqlite_foreach_selected_row (db, sql, load_property_cb, &value) < 0) {
seaf_warning ("Error read property %s for repo %s.\n", key, repo_id);
pthread_mutex_unlock (&manager->priv->db_lock);
return NULL;
}
pthread_mutex_unlock (&manager->priv->db_lock);
return value;
}
static gboolean
load_branch_cb (sqlite3_stmt *stmt, void *vrepo)
{
SeafRepo *repo = vrepo;
SeafRepoManager *manager = repo->manager;
char *branch_name = (char *) sqlite3_column_text (stmt, 0);
SeafBranch *branch =
seaf_branch_manager_get_branch (manager->seaf->branch_mgr,
repo->id, branch_name);
if (branch == NULL) {
seaf_warning ("Broken branch name for repo %s\n", repo->id);
repo->is_corrupted = TRUE;
return FALSE;
}
load_repo_commit (manager, repo, branch);
seaf_branch_unref (branch);
/* Only one result. */
return FALSE;
}
static gboolean
load_account_info_cb (sqlite3_stmt *stmt, void *vrepo)
{
SeafRepo *repo = vrepo;
const char *server, *user;
server = (const char *)sqlite3_column_text(stmt, 0);
user = (const char *)sqlite3_column_text(stmt, 1);
repo->server = g_strdup (server);
repo->user = g_strdup (user);
return FALSE;
}
static int
load_repo_account_info (SeafRepoManager *manager, SeafRepo *repo)
{
sqlite3 *db = manager->priv->db;
char sql[256];
int n;
pthread_mutex_lock (&manager->priv->db_lock);
snprintf (sql, sizeof(sql),
"SELECT server, username FROM AccountRepos WHERE repo_id='%s'",
repo->id);
n = sqlite_foreach_selected_row (db, sql, load_account_info_cb, repo);
if (n < 0) {
pthread_mutex_unlock (&manager->priv->db_lock);
return -1;
}
pthread_mutex_unlock (&manager->priv->db_lock);
return 0;
}
static SeafRepo *
load_repo (SeafRepoManager *manager, const char *repo_id)
{
char sql[256];
SeafRepo *repo = seaf_repo_new(repo_id, NULL, NULL);
if (!repo) {
seaf_warning ("[repo mgr] failed to alloc repo.\n");
return NULL;
}
repo->manager = manager;
snprintf(sql, 256, "SELECT branch_name FROM RepoBranch WHERE repo_id='%s'",
repo->id);
if (sqlite_foreach_selected_row (manager->priv->db, sql,
load_branch_cb, repo) <= 0) {
seaf_warning ("Error read branch for repo %s.\n", repo->id);
seaf_repo_free (repo);
return NULL;
}
/* If repo head is set but failed to load branch or commit. */
if (repo->is_corrupted) {
seaf_repo_free (repo);
return NULL;
}
load_repo_passwd (manager, repo);
load_repo_account_info (manager, repo);
char *value;
repo->token = load_repo_property (manager, repo->id, REPO_PROP_TOKEN);
/* load readonly property */
value = load_repo_property (manager, repo->id, REPO_PROP_IS_READONLY);
if (g_strcmp0(value, "true") == 0)
repo->is_readonly = TRUE;
else
repo->is_readonly = FALSE;
g_free (value);
g_hash_table_insert (manager->priv->repo_hash, g_strdup(repo->id), repo);
seaf_repo_ref (repo);
return repo;
}
static sqlite3*
open_db (SeafRepoManager *manager, const char *seaf_dir)
{
sqlite3 *db;
char *db_path;
db_path = g_build_filename (seaf_dir, "repo.db", NULL);
if (sqlite_open_db (db_path, &db) < 0)
return NULL;
g_free (db_path);
manager->priv->db = db;
char *sql = "CREATE TABLE IF NOT EXISTS Repo (repo_id TEXT PRIMARY KEY);";
sqlite_query_exec (db, sql);
sql = "CREATE TABLE IF NOT EXISTS DeletedRepo (repo_id TEXT PRIMARY KEY, remove_cache INTEGER);";
sqlite_query_exec (db, sql);
/* Locally cache repo list for each account. */
sql = "CREATE TABLE IF NOT EXISTS AccountRepos "
"(server TEXT, username TEXT, repo_id TEXT, "
"name TEXT, display_name TEXT, mtime INTEGER);";
sqlite_query_exec (db, sql);
sql = "CREATE UNIQUE INDEX IF NOT EXISTS AccountRepoIdIdx on AccountRepos "
"(server, username, repo_id);";
sqlite_query_exec (db, sql);
sql = "CREATE UNIQUE INDEX IF NOT EXISTS AccountRepoDisplayNameIdx on AccountRepos "
"(server, username, display_name);";
sqlite_query_exec (db, sql);
sql = "CREATE TABLE IF NOT EXISTS AccountSpace "
"(server TEXT, username TEXT, total INTEGER, used INTEGER, "
"PRIMARY KEY (server, username));";
sqlite_query_exec (db, sql);
sql = "CREATE TABLE IF NOT EXISTS RepoBranch ("
"repo_id TEXT PRIMARY KEY, branch_name TEXT);";
sqlite_query_exec (db, sql);
sql = "CREATE TABLE IF NOT EXISTS RepoKeys "
"(repo_id TEXT PRIMARY KEY, key TEXT NOT NULL, iv TEXT NOT NULL);";
sqlite_query_exec (db, sql);
sql = "CREATE TABLE IF NOT EXISTS RepoProperty ("
"repo_id TEXT, key TEXT, value TEXT);";
sqlite_query_exec (db, sql);
sql = "CREATE INDEX IF NOT EXISTS RepoIndex ON RepoProperty (repo_id);";
sqlite_query_exec (db, sql);
sql = "CREATE TABLE IF NOT EXISTS FolderUserPerms ("
"repo_id TEXT, path TEXT, permission TEXT);";
sqlite_query_exec (db, sql);
sql = "CREATE INDEX IF NOT EXISTS folder_user_perms_repo_id_idx "
"ON FolderUserPerms (repo_id);";
sqlite_query_exec (db, sql);
sql = "CREATE TABLE IF NOT EXISTS FolderGroupPerms ("
"repo_id TEXT, path TEXT, permission TEXT);";
sqlite_query_exec (db, sql);
sql = "CREATE INDEX IF NOT EXISTS folder_group_perms_repo_id_idx "
"ON FolderGroupPerms (repo_id);";
sqlite_query_exec (db, sql);
sql = "CREATE TABLE IF NOT EXISTS FolderPermTimestamp ("
"repo_id TEXT, timestamp INTEGER, PRIMARY KEY (repo_id));";
sqlite_query_exec (db, sql);
sql = "CREATE TABLE IF NOT EXISTS LockedFiles (repo_id TEXT, path TEXT, "
"operation TEXT, old_mtime INTEGER, file_id TEXT, new_path TEXT, "
"PRIMARY KEY (repo_id, path));";
sqlite_query_exec (db, sql);
return db;
}
static gboolean
load_repo_cb (sqlite3_stmt *stmt, void *vmanager)
{
SeafRepoManager *manager = vmanager;
const char *repo_id;
repo_id = (const char *) sqlite3_column_text (stmt, 0);
load_repo (manager, repo_id);
return TRUE;
}
static gboolean
mark_repo_deleted (sqlite3_stmt *stmt, void *vmanager)
{
SeafRepoManager *manager = vmanager;
const char *repo_id;
int remove_cache;
SeafRepo *repo;
repo_id = (const char *) sqlite3_column_text (stmt, 0);
remove_cache = sqlite3_column_int (stmt, 1);
repo = seaf_repo_manager_get_repo (manager, repo_id);
if (!repo)
return TRUE;
repo->delete_pending = TRUE;
repo->remove_cache = remove_cache;
seaf_repo_unref (repo);
return TRUE;
}
static void
load_repos (SeafRepoManager *manager, const char *seaf_dir)
{
sqlite3 *db = open_db(manager, seaf_dir);
if (!db) return;
char *sql;
sql = "SELECT repo_id FROM Repo;";
if (sqlite_foreach_selected_row (db, sql, load_repo_cb, manager) < 0) {
seaf_warning ("Error read repo db.\n");
return;
}
sql = "SELECT repo_id, remove_cache FROM DeletedRepo";
if (sqlite_foreach_selected_row (db, sql, mark_repo_deleted, manager) < 0) {
seaf_warning ("Error mark repos deleted.\n");
return;
}
}
static void
save_repo_property (SeafRepoManager *manager,
const char *repo_id,
const char *key, const char *value)
{
char *sql;
sqlite3 *db = manager->priv->db;
pthread_mutex_lock (&manager->priv->db_lock);
sql = sqlite3_mprintf ("SELECT repo_id FROM RepoProperty WHERE repo_id=%Q AND key=%Q",
repo_id, key);
if (sqlite_check_for_existence(db, sql)) {
sqlite3_free (sql);
sql = sqlite3_mprintf ("UPDATE RepoProperty SET value=%Q"
"WHERE repo_id=%Q and key=%Q",
value, repo_id, key);
sqlite_query_exec (db, sql);
sqlite3_free (sql);
} else {
sqlite3_free (sql);
sql = sqlite3_mprintf ("INSERT INTO RepoProperty VALUES (%Q, %Q, %Q)",
repo_id, key, value);
sqlite_query_exec (db, sql);
sqlite3_free (sql);
}
pthread_mutex_unlock (&manager->priv->db_lock);
}
int
seaf_repo_manager_set_repo_property (SeafRepoManager *manager,
const char *repo_id,
const char *key,
const char *value)
{
if (!seaf_repo_manager_repo_exists (manager, repo_id))
return -1;
save_repo_property (manager, repo_id, key, value);
return 0;
}
char *
seaf_repo_manager_get_repo_property (SeafRepoManager *manager,
const char *repo_id,
const char *key)
{
return load_repo_property (manager, repo_id, key);
}
static void
seaf_repo_manager_del_repo_property (SeafRepoManager *manager,
const char *repo_id)
{
char *sql;
sqlite3 *db = manager->priv->db;
pthread_mutex_lock (&manager->priv->db_lock);
sql = sqlite3_mprintf ("DELETE FROM RepoProperty WHERE repo_id = %Q", repo_id);
sqlite_query_exec (db, sql);
sqlite3_free (sql);
pthread_mutex_unlock (&manager->priv->db_lock);
}
static void
seaf_repo_manager_del_repo_property_by_key (SeafRepoManager *manager,
const char *repo_id,
const char *key)
{
char *sql;
sqlite3 *db = manager->priv->db;
pthread_mutex_lock (&manager->priv->db_lock);
sql = sqlite3_mprintf ("DELETE FROM RepoProperty "
"WHERE repo_id = %Q "
" AND key = %Q", repo_id, key);
sqlite_query_exec (db, sql);
sqlite3_free (sql);
pthread_mutex_unlock (&manager->priv->db_lock);
}
static int
save_repo_enc_info (SeafRepoManager *manager,
SeafRepo *repo)
{
sqlite3 *db = manager->priv->db;
char sql[512];
char key[65], iv[33];
if (repo->enc_version == 1) {
rawdata_to_hex (repo->enc_key, key, 16);
rawdata_to_hex (repo->enc_iv, iv, 16);
} else if (repo->enc_version >= 2) {
rawdata_to_hex (repo->enc_key, key, 32);
rawdata_to_hex (repo->enc_iv, iv, 16);
}
snprintf (sql, sizeof(sql), "REPLACE INTO RepoKeys VALUES ('%s', '%s', '%s')",
repo->id, key, iv);
if (sqlite_query_exec (db, sql) < 0)
return -1;
return 0;
}
GList*
seaf_repo_manager_get_repo_list (SeafRepoManager *manager, int start, int limit)
{
GList *repo_list = NULL;
GHashTableIter iter;
SeafRepo *repo;
gpointer key, value;
if (pthread_rwlock_rdlock (&manager->priv->lock) < 0) {
seaf_warning ("[repo mgr] failed to lock repo cache.\n");
return NULL;
}
g_hash_table_iter_init (&iter, manager->priv->repo_hash);
while (g_hash_table_iter_next (&iter, &key, &value)) {
repo = value;
if (!repo->delete_pending) {
repo_list = g_list_prepend (repo_list, repo);
seaf_repo_ref (repo);
}
}
pthread_rwlock_unlock (&manager->priv->lock);
return repo_list;
}
GList*
seaf_repo_manager_get_enc_repo_list (SeafRepoManager *manager, int start, int limit)
{
GList *repo_list = NULL;
GHashTableIter iter;
SeafRepo *repo;
gpointer key, value;
if (pthread_rwlock_rdlock (&manager->priv->lock) < 0) {
seaf_warning ("[repo mgr] failed to lock repo cache.\n");
return NULL;
}
g_hash_table_iter_init (&iter, manager->priv->repo_hash);
while (g_hash_table_iter_next (&iter, &key, &value)) {
repo = value;
if (!repo->delete_pending && repo->encrypted) {
repo_list = g_list_prepend (repo_list, repo);
seaf_repo_ref (repo);
}
}
pthread_rwlock_unlock (&manager->priv->lock);
return repo_list;
}
gboolean
seaf_repo_manager_is_repo_delete_pending (SeafRepoManager *manager, const char *id)
{
SeafRepo *repo;
gboolean ret = FALSE;
pthread_rwlock_rdlock (&manager->priv->lock);
repo = g_hash_table_lookup (manager->priv->repo_hash, id);
if (repo && repo->delete_pending)
ret = TRUE;
pthread_rwlock_unlock (&manager->priv->lock);
return ret;
}
int
seaf_repo_manager_set_repo_token (SeafRepoManager *manager,
SeafRepo *repo,
const char *token)
{
g_free (repo->token);
repo->token = g_strdup(token);
save_repo_property (manager, repo->id, REPO_PROP_TOKEN, token);
return 0;
}
int
seaf_repo_manager_remove_repo_token (SeafRepoManager *manager,
SeafRepo *repo)
{
g_free (repo->token);
repo->token = NULL;
seaf_repo_manager_del_repo_property_by_key(manager, repo->id, REPO_PROP_TOKEN);
return 0;
}
void
seaf_repo_manager_rename_repo (SeafRepoManager *manager,
const char *repo_id,
const char *new_name)
{
SeafRepo *repo;
pthread_rwlock_wrlock (&manager->priv->lock);
repo = g_hash_table_lookup (manager->priv->repo_hash, repo_id);
if (repo) {
g_free (repo->name);
repo->name = g_strdup (new_name);
}
pthread_rwlock_unlock (&manager->priv->lock);
}
/* Account cache related functions. */
void
seaf_account_free (SeafAccount *account)
{
if (!account)
return;
g_free (account->server);
g_free (account->username);
g_free (account->nickname);
g_free (account->token);
g_free (account->fileserver_addr);
g_free (account->unique_id);
g_free (account);
}
static const char *repo_type_strings[] = {
"",
"My Libraries",
"Shared with me",
"Shared with groups",
"Shared with all",
NULL
};
static const char *repo_type_strings_zh[] = {
"",
"我的资料库",
"共享资料库",
"群组资料库",
"公共资料库",
NULL
};
static const char *repo_type_strings_fr[] = {
"",
"Mes bibliothèques",
"Partagé avec moi",
"Partagé avec des groupes",
"Partagé avec tout le monde",
NULL
};
static const char *repo_type_strings_de[] = {
"",
"Meine Bibliotheken",
"Für mich freigegeben",
"Für meine Gruppen",
"Für alle freigegeben",
NULL
};
static const char **
get_repo_type_string_table ()
{
const char **string_table;
if (seaf->language == SEAF_LANG_ZH_CN)
string_table = repo_type_strings_zh;
else if (seaf->language == SEAF_LANG_FR_FR)
string_table = repo_type_strings_fr;
else if (seaf->language == SEAF_LANG_DE_DE)
string_table = repo_type_strings_de;
else
string_table = repo_type_strings;
return string_table;
}
RepoType
repo_type_from_string (const char *type_str)
{
const char *str;
int i;
RepoType type = REPO_TYPE_UNKNOWN;
const char **string_table = get_repo_type_string_table ();
for (i = 0; i < N_REPO_TYPE; ++i) {
str = string_table[i];
if (g_strcmp0 (type_str, str) == 0) {
type = i;
break;
}
}
return type;
}
GList *
repo_type_string_list ()
{
GList *ret = NULL;
const char *str;
int i = 0;
const char **string_table = get_repo_type_string_table ();
for (i = 1; i < N_REPO_TYPE; ++i) {
str = string_table[i];
ret = g_list_append (ret, g_strdup(str));
}
return ret;
}
RepoInfo *
repo_info_new (const char *id, const char *head_commit_id,
const char *name, gint64 mtime, gboolean is_readonly)
{
RepoInfo *ret = g_new0 (RepoInfo, 1);
memcpy (ret->id, id, 36);
ret->head_commit_id = g_strdup(head_commit_id);
ret->name = g_strdup(name);
ret->mtime = mtime;
ret->is_readonly = is_readonly;
return ret;
}
RepoInfo *
repo_info_copy (RepoInfo *info)
{
RepoInfo *ret = g_new0 (RepoInfo, 1);
memcpy (ret->id, info->id, 36);
ret->head_commit_id = g_strdup(info->head_commit_id);
ret->name = g_strdup(info->name);
ret->display_name = g_strdup(info->display_name);
ret->mtime = info->mtime;
ret->is_readonly = info->is_readonly;
ret->is_corrupted = info->is_corrupted;
ret->type = info->type;
return ret;
}
void
repo_info_free (RepoInfo *info)
{
if (!info)
return;
g_free (info->head_commit_id);
g_free (info->name);
g_free (info->display_name);
g_free (info);
}
typedef struct _LoadRepoInfoRes {
GHashTable *repos;
GHashTable *name_to_repo;
} LoadRepoInfoRes;
static RepoType
repo_type_from_display_name (const char *display_name)
{
char **tokens;
char *type_str;
RepoType type = REPO_TYPE_UNKNOWN;
const char **string_table = get_repo_type_string_table ();
int i;
tokens = g_strsplit (display_name, "/", 0);
if (g_strv_length (tokens) > 1) {
type_str = tokens[0];
for (i = 1; i < N_REPO_TYPE; ++i) {
if (strcmp (type_str, string_table[i]) == 0)
type = i;
}
}
g_strfreev (tokens);
return type;
}
static gboolean
load_repo_info_cb (sqlite3_stmt *stmt, void *data)
{
LoadRepoInfoRes *res = data;
GHashTable *repos = res->repos;
const char *repo_id, *name, *display_name;
gint64 mtime;
SeafRepo* repo = NULL;
repo_id = (const char *)sqlite3_column_text (stmt, 0);
name = (const char *)sqlite3_column_text (stmt, 1);
display_name = (const char *)sqlite3_column_text (stmt, 2);
mtime = (gint64)sqlite3_column_int64 (stmt, 3);
RepoInfo *info = g_new0 (RepoInfo, 1);
memcpy (info->id, repo_id, 36);
info->name = g_strdup(name);
info->display_name = g_strdup(display_name);
info->mtime = mtime;
info->type = repo_type_from_display_name (display_name);
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo) {
seaf_warning ("Failed to find repo %s.\n", repo_id);
} else {
info->is_readonly = repo->is_readonly;
seaf_repo_unref(repo);
}
g_hash_table_insert (repos, g_strdup(repo_id), info);
return TRUE;
}
static int
load_current_account_repo_info (SeafRepoManager *mgr,
const char *server,
const char *username,
GHashTable *repos)
{
LoadRepoInfoRes res;
res.repos = repos;
pthread_mutex_lock (&mgr->priv->db_lock);
char *sql = sqlite3_mprintf ("SELECT repo_id, name, display_name, mtime "
"FROM AccountRepos WHERE "
"server='%q' AND username='%q'",
server, username);
if (sqlite_foreach_selected_row (mgr->priv->db, sql,
load_repo_info_cb, &res) < 0) {
sqlite3_free (sql);
pthread_mutex_unlock (&mgr->priv->db_lock);
return -1;
}
pthread_mutex_unlock (&mgr->priv->db_lock);
sqlite3_free (sql);
return 0;
}
/* static void */
/* update_file_sync_status_cb (const char *repo_id, */
/* const char *file_path, */
/* SeafStat *st, */
/* void *user_data) */
/* { */
/* if (file_cache_mgr_is_file_cached (seaf->file_cache_mgr, */
/* repo_id, file_path) && */
/* !file_cache_mgr_is_file_changed (seaf->file_cache_mgr, */
/* repo_id, file_path, FALSE)) { */
/* seaf_sync_manager_update_active_path (seaf->sync_mgr, repo_id, */
/* file_path, st->st_mode, SYNC_STATUS_SYNCED); */
/* } */
/* } */
static void
load_repo_fs_worker (gpointer data, gpointer user_data)
{
SeafRepo *repo = (SeafRepo *)data;
seaf_repo_load_fs (repo, FALSE);
seaf_repo_unref (repo);
}
#define MAX_LOAD_REPO_TREE_THREADS 5
static void *
load_repo_file_systems_thread (void *vdata)
{
AccountInfo *account_info = vdata;
GList *info_list, *ptr;
RepoInfo *info;
SeafRepo *repo;
GThreadPool *pool;
pool = g_thread_pool_new (load_repo_fs_worker,
NULL,
MAX_LOAD_REPO_TREE_THREADS,
FALSE,
NULL);
if (!pool) {
seaf_warning ("Failed to create thread pool.\n");
goto out;
}
info_list = seaf_repo_manager_get_account_repos (seaf->repo_mgr, account_info->server, account_info->username);
for (ptr = info_list; ptr; ptr = ptr->next) {
info = (RepoInfo *)ptr->data;
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, info->id);
if (!repo) {
continue;
}
seaf_repo_set_worktree (repo, info->display_name);
g_thread_pool_push (pool, repo, NULL);
}
/* Wait until all tasks being done. */
g_thread_pool_free (pool, FALSE, TRUE);
g_list_free_full (info_list, (GDestroyNotify)repo_info_free);
/* for (ptr = info_list; ptr; ptr = ptr->next) { */
/* info = (RepoInfo *)ptr->data; */
/* file_cache_mgr_traverse_path (seaf->file_cache_mgr, info->id, "", */
/* update_file_sync_status_cb, NULL, NULL); */
/* } */
out:
account_info_free (account_info);
return NULL;
}
static char *
create_unique_id (const char *server, const char *username)
{
char *id = g_strconcat (server, "_", username, NULL);
char *p;
for (p = id; *p != '\0'; ++p) {
if (*p == ':' || *p == '/')
*p = '_';
}
return id;
}
void
account_info_free (AccountInfo *info)
{
if (!info)
return;
g_free (info->server);
g_free (info->username);
g_free (info);
}
AccountInfo *
seaf_repo_manager_get_account_info_by_name (SeafRepoManager *mgr,
const char *name)
{
GHashTableIter iter;
gpointer key, value;
SeafAccount *account = NULL;
AccountInfo *account_info = NULL;
pthread_rwlock_rdlock (&mgr->priv->account_lock);
g_hash_table_iter_init (&iter, mgr->priv->accounts);
while (g_hash_table_iter_next (&iter, &key, &value)) {
account = (SeafAccount *)value;
if (g_strcmp0 (account->name, name) == 0) {
account_info = g_new0 (AccountInfo, 1);
account_info->server = g_strdup (account->server);
account_info->username = g_strdup (account->username);
break;
}
}
pthread_rwlock_unlock (&mgr->priv->account_lock);
return account_info;
}
static void
add_repo_info (SeafRepoManager *mgr, char *account_key, GHashTable *new_repos)
{
GHashTable *new_name_to_repo;
RepoInfo *info, *copy;
GHashTableIter iter;
gpointer key, value;
new_name_to_repo = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
g_hash_table_iter_init (&iter, new_repos);
while (g_hash_table_iter_next (&iter, &key, &value)) {
info = value;
copy = repo_info_copy (info);
g_hash_table_insert (mgr->priv->repo_infos, g_strdup(copy->id), copy);
g_hash_table_insert (new_name_to_repo, g_strdup(copy->display_name), copy);
}
g_hash_table_insert (mgr->priv->repo_names, g_strdup(account_key), new_name_to_repo);
return;
}
int
seaf_repo_manager_add_account (SeafRepoManager *mgr,
const char *server,
const char *username,
const char *nickname,
const char *token,
const char *name,
gboolean is_pro)
{
SeafAccount *account = NULL;
SeafAccount *new_account = NULL;
GHashTable *new_repos;
seaf_message ("adding account %s %s %s.\n", server, username, name);
account = seaf_repo_manager_get_account (seaf->repo_mgr, server, username);
if (account) {
seaf_account_free (account);
return 0;
}
new_repos = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
(GDestroyNotify)repo_info_free);
if (load_current_account_repo_info (mgr, server, username,
new_repos ) < 0) {
g_hash_table_destroy (new_repos);
return -1;
}
char *account_key = g_strconcat (server, "_", username, NULL);
new_account = g_new0 (SeafAccount, 1);
new_account->server = g_strdup(server);
new_account->username = g_strdup(username);
new_account->nickname = g_strdup(nickname);
new_account->token = g_strdup(token);
new_account->name = g_strdup(name);
new_account->fileserver_addr = parse_fileserver_addr(server);
new_account->is_pro = is_pro;
new_account->unique_id = create_unique_id (server, username);
new_account->repo_list_fetched = FALSE;
new_account->all_repos_loaded = FALSE;
pthread_rwlock_wrlock (&mgr->priv->account_lock);
add_repo_info (mgr, account_key, new_repos);
g_hash_table_insert (mgr->priv->accounts, g_strdup(account_key), new_account);
pthread_rwlock_unlock (&mgr->priv->account_lock);
g_hash_table_destroy (new_repos);
g_free (account_key);
seaf->last_check_repo_list_time = 0;
seaf->last_access_fs_time = 0;
/* Update current repo list immediately after account switch. */
seaf_sync_manager_update_account_repo_list (seaf->sync_mgr, server, username);
pthread_t tid;
AccountInfo *account_info = g_new0(AccountInfo, 1);
account_info->server = g_strdup (server);
account_info->username = g_strdup (username);
int rc = pthread_create (&tid, NULL, load_repo_file_systems_thread, account_info);
if (rc != 0) {
seaf_warning ("Failed to start load repo fs thread: %s\n", strerror(rc));
}
return 0;
}
static
SeafAccount *
copy_account (SeafAccount *account)
{
SeafAccount *ret = g_new0 (SeafAccount, 1);
ret->server = g_strdup(account->server);
ret->username = g_strdup(account->username);
ret->nickname = g_strdup(account->nickname);
ret->token = g_strdup(account->token);
ret->name = g_strdup (account->name);
ret->fileserver_addr = g_strdup(account->fileserver_addr);
ret->is_pro = account->is_pro;
ret->unique_id = g_strdup(account->unique_id);
ret->repo_list_fetched = account->repo_list_fetched;
ret->all_repos_loaded = account->all_repos_loaded;
ret->server_disconnected = account->server_disconnected;
return ret;
}
SeafAccount *
seaf_repo_manager_get_account (SeafRepoManager *mgr,
const char *server,
const char *username)
{
SeafAccount *account = NULL, *ret = NULL;
if (!server && !username) {
return NULL;
}
char *key = g_strconcat (server, "_", username, NULL);
pthread_rwlock_rdlock (&mgr->priv->account_lock);
account = g_hash_table_lookup (mgr->priv->accounts, key);
if (!account) {
goto out;
}
ret = copy_account (account);
out:
pthread_rwlock_unlock (&mgr->priv->account_lock);
g_free (key);
return ret;
}
gboolean
seaf_repo_manager_account_exists (SeafRepoManager *mgr,
const char *server,
const char *username)
{
SeafAccount *account = NULL;
gboolean exists = TRUE;
char *key = g_strconcat (server, "_", username, NULL);
pthread_rwlock_rdlock (&mgr->priv->account_lock);
account = g_hash_table_lookup (mgr->priv->accounts, key);
if (!account) {
exists = FALSE;
goto out;
}
out:
pthread_rwlock_unlock (&mgr->priv->account_lock);
g_free (key);
return exists;
}
GList *
seaf_repo_manager_get_account_list (SeafRepoManager *mgr)
{
GList *ret = NULL;
GHashTableIter iter;
gpointer key, value;
SeafAccount *account = NULL;
pthread_rwlock_rdlock (&mgr->priv->account_lock);
g_hash_table_iter_init (&iter, mgr->priv->accounts);
while (g_hash_table_iter_next (&iter, &key, &value)) {
account = copy_account ((SeafAccount *)value);
ret = g_list_prepend (ret, account);
}
pthread_rwlock_unlock (&mgr->priv->account_lock);
return ret;
}
gboolean
seaf_repo_manager_account_is_pro (SeafRepoManager *mgr,
const char *server,
const char *user)
{
gboolean ret = FALSE;
SeafAccount *account = seaf_repo_manager_get_account (mgr, server, user);
if (account) {
ret = account->is_pro;
}
seaf_account_free (account);
return ret;
}
void
seaf_repo_manager_set_repo_list_fetched (SeafRepoManager *mgr,
const char *server,
const char *username)
{
SeafAccount *account = NULL;
char *key = g_strconcat (server, "_", username, NULL);
pthread_rwlock_wrlock (&mgr->priv->account_lock);
account = g_hash_table_lookup (mgr->priv->accounts, key);
if (!account) {
goto out;
}
account->repo_list_fetched = TRUE;
out:
pthread_rwlock_unlock (&mgr->priv->account_lock);
g_free (key);
}
void
seaf_repo_manager_set_account_all_repos_loaded (SeafRepoManager *mgr,
const char *server,
const char *username)
{
SeafAccount *account = NULL;
char *key = g_strconcat (server, "_", username, NULL);
pthread_rwlock_wrlock (&mgr->priv->account_lock);
account = g_hash_table_lookup (mgr->priv->accounts, key);
if (!account) {
goto out;
}
account->all_repos_loaded = TRUE;
out:
pthread_rwlock_unlock (&mgr->priv->account_lock);
g_free (key);
}
void
seaf_repo_manager_set_account_server_disconnected (SeafRepoManager *mgr,
const char *server,
const char *username,
gboolean server_disconnected)
{
SeafAccount *account = NULL;
char *key = g_strconcat (server, "_", username, NULL);
pthread_rwlock_wrlock (&mgr->priv->account_lock);
account = g_hash_table_lookup (mgr->priv->accounts, key);
if (!account) {
goto out;
}
account->server_disconnected = server_disconnected;
out:
pthread_rwlock_unlock (&mgr->priv->account_lock);
g_free (key);
}
static int
delete_account_repos_from_db (SeafRepoManager *mgr,
const char *server,
const char *username)
{
char *sql;
int ret;
pthread_mutex_lock (&mgr->priv->db_lock);
sql = sqlite3_mprintf ("DELETE FROM AccountRepos WHERE server='%q' AND username='%q'",
server, username);
ret = sqlite_query_exec (mgr->priv->db, sql);
pthread_mutex_unlock (&mgr->priv->db_lock);
sqlite3_free (sql);
return ret;
}
static gboolean
collect_account_repo_cb (sqlite3_stmt *stmt, void *data)
{
GList **prepos = data;
const char *repo_id;
repo_id = (const char *)sqlite3_column_text (stmt, 0);
*prepos = g_list_prepend (*prepos, g_strdup(repo_id));
return TRUE;
}
static int
delete_account_repos (SeafRepoManager *mgr, const char *server, const char *username,
gboolean remove_cache)
{
char *sql;
GList *repos = NULL, *ptr;
char *repo_id;
SeafRepo *repo;
int ret = 0;
pthread_mutex_lock (&mgr->priv->db_lock);
sql = sqlite3_mprintf ("SELECT repo_id FROM AccountRepos WHERE "
"server='%q' AND username='%q'",
server, username);
if (sqlite_foreach_selected_row (mgr->priv->db, sql, collect_account_repo_cb, &repos) < 0) {
ret = -1;
goto out;
}
pthread_mutex_unlock (&mgr->priv->db_lock);
for (ptr = repos; ptr; ptr = ptr->next) {
repo_id = ptr->data;
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (repo) {
seaf_repo_manager_mark_repo_deleted (seaf->repo_mgr, repo, remove_cache);
http_tx_manager_cancel_task (seaf->http_tx_mgr, repo->id,
HTTP_TASK_TYPE_DOWNLOAD);
http_tx_manager_cancel_task (seaf->http_tx_mgr, repo->id,
HTTP_TASK_TYPE_UPLOAD);
}
seaf_repo_unref (repo);
}
g_list_free_full (repos, g_free);
delete_account_repos_from_db (mgr, server, username);
out:
sqlite3_free (sql);
return ret;
}
int
seaf_repo_manager_delete_account (SeafRepoManager *mgr,
const char *server,
const char *username,
gboolean remove_cache,
GError **error)
{
SeafAccount *account = NULL;
GHashTable *name_to_repo = NULL;
GHashTableIter iter;
gpointer key, value;
RepoInfo *info;
int ret = 0;
char *account_key = g_strconcat (server, "_", username, NULL);
pthread_rwlock_wrlock (&mgr->priv->account_lock);
account = g_hash_table_lookup (mgr->priv->accounts, account_key);
if (!account) {
pthread_rwlock_unlock (&mgr->priv->account_lock);
g_free (account_key);
return 0;
}
name_to_repo = g_hash_table_lookup (mgr->priv->repo_names, account_key);
g_hash_table_iter_init (&iter, name_to_repo);
while (g_hash_table_iter_next (&iter, &key, &value)) {
info = value;
g_hash_table_remove (mgr->priv->repo_infos, info->id);
}
g_hash_table_remove (mgr->priv->repo_names, account_key);
g_hash_table_remove (mgr->priv->accounts, account_key);
pthread_rwlock_unlock (&mgr->priv->account_lock);
if (delete_account_repos (mgr, server, username, remove_cache) < 0) {
ret = -1;
}
g_free (account_key);
return ret;
}
GList *
seaf_repo_manager_get_account_repos (SeafRepoManager *mgr,
const char *server,
const char *user)
{
GList *ret = NULL;
GHashTable *name_to_repo = NULL;
GHashTableIter iter;
gpointer key, value;
RepoInfo *info, *copy;
char *account_key = NULL;
account_key = g_strconcat (server, "_", user, NULL);
pthread_rwlock_rdlock (&mgr->priv->account_lock);
name_to_repo = g_hash_table_lookup (mgr->priv->repo_names, account_key);
if (!name_to_repo) {
pthread_rwlock_unlock (&mgr->priv->account_lock);
g_free (account_key);
return NULL;
}
g_hash_table_iter_init (&iter, name_to_repo);
while (g_hash_table_iter_next (&iter, &key, &value)) {
info = value;
copy = repo_info_copy (info);
ret = g_list_prepend (ret, copy);
}
ret = g_list_reverse (ret);
pthread_rwlock_unlock (&mgr->priv->account_lock);
return ret;
}
GList *
seaf_repo_manager_get_account_repo_ids (SeafRepoManager *mgr,
const char *server,
const char *user)
{
GList *ret = NULL;
GHashTable *name_to_repo = NULL;
GHashTableIter iter;
gpointer key, value;
RepoInfo *info;
char *account_key = NULL;
account_key = g_strconcat (server, "_", user, NULL);
pthread_rwlock_rdlock (&mgr->priv->account_lock);
name_to_repo = g_hash_table_lookup (mgr->priv->repo_names, account_key);
if (!name_to_repo) {
pthread_rwlock_unlock (&mgr->priv->account_lock);
g_free (account_key);
return NULL;
}
g_hash_table_iter_init (&iter, name_to_repo);
while (g_hash_table_iter_next (&iter, &key, &value)) {
info = value;
ret = g_list_prepend (ret, g_strdup (info->id));
}
pthread_rwlock_unlock (&mgr->priv->account_lock);
return ret;
}
RepoInfo *
seaf_repo_manager_get_repo_info_by_display_name (SeafRepoManager *mgr,
const char *server,
const char *user,
const char *name)
{
GHashTable *name_to_repo = NULL;
RepoInfo *info, *ret = NULL;
char *account_key = NULL;
if (!seaf_repo_manager_account_exists (mgr, server, user)) {
return NULL;
}
account_key = g_strconcat (server, "_", user, NULL);
pthread_rwlock_rdlock (&mgr->priv->account_lock);
name_to_repo = g_hash_table_lookup (mgr->priv->repo_names, account_key);
info = g_hash_table_lookup (name_to_repo, name);
if (info)
ret = repo_info_copy (info);
pthread_rwlock_unlock (&mgr->priv->account_lock);
g_free (account_key);
return ret;
}
char *
seaf_repo_manager_get_repo_id_by_display_name (SeafRepoManager *mgr,
const char *server,
const char *user,
const char *name)
{
GHashTable *name_to_repo = NULL;
RepoInfo *info;
char *repo_id = NULL;
char *account_key = NULL;
if (!seaf_repo_manager_account_exists (mgr, server, user)) {
return NULL;
}
account_key = g_strconcat (server, "_", user, NULL);
pthread_rwlock_rdlock (&mgr->priv->account_lock);
name_to_repo = g_hash_table_lookup (mgr->priv->repo_names, account_key);
info = g_hash_table_lookup (name_to_repo, name);
if (info)
repo_id = g_strdup (info->id);
pthread_rwlock_unlock (&mgr->priv->account_lock);
g_free (account_key);
return repo_id;
}
char *
seaf_repo_manager_get_repo_display_name (SeafRepoManager *mgr,
const char *id)
{
RepoInfo *info;
char *display_name = NULL;
pthread_rwlock_rdlock (&mgr->priv->account_lock);
info = g_hash_table_lookup (mgr->priv->repo_infos, id);
if (info)
display_name = g_strdup(info->display_name);
pthread_rwlock_unlock (&mgr->priv->account_lock);
return display_name;
}
static char *
find_unique_display_name (GHashTable *name_to_repo, RepoType type, const char *name)
{
char *base_display_name, *display_name;
int n = 0;
const char **string_table = get_repo_type_string_table ();
if (type <= REPO_TYPE_UNKNOWN || type >= N_REPO_TYPE) {
base_display_name = g_strdup(name);
} else {
base_display_name = g_build_path ("/", string_table[type], name, NULL);
}
display_name = g_strdup(base_display_name);
while (g_hash_table_lookup (name_to_repo, display_name) != NULL) {
++n;
g_free (display_name);
display_name = g_strdup_printf ("%s (%d)", base_display_name, n);
}
g_free (base_display_name);
return display_name;
}
static int
remove_repo_from_account (sqlite3 *db,
const char *server,
const char *username,
const char *repo_id)
{
char *sql;
int ret = 0;
sql = sqlite3_mprintf ("DELETE FROM AccountRepos WHERE "
"server='%q' AND username='%q' AND repo_id='%q'",
server, username, repo_id);
ret = sqlite_query_exec (db, sql);
sqlite3_free (sql);
return ret;
}
static int
add_repo_to_account (sqlite3 *db,
const char *server,
const char *username,
RepoInfo *info)
{
char *sql;
int ret = 0;
sql = sqlite3_mprintf ("INSERT INTO AccountRepos VALUES "
"('%q', '%q', '%q', '%q', '%q', %lld)",
server, username, info->id,
info->name, info->display_name, info->mtime);
ret = sqlite_query_exec (db, sql);
sqlite3_free (sql);
return ret;
}
static int
update_repo_to_account (sqlite3 *db,
const char *server,
const char *username,
RepoInfo *info)
{
char *sql;
int ret = 0;
sql = sqlite3_mprintf ("UPDATE AccountRepos SET name = '%q', display_name = '%q', "
"mtime = %lld"
" WHERE server = '%q' AND username = '%q' AND repo_id = '%q'",
info->name, info->display_name, info->mtime,
server, username, info->id);
ret = sqlite_query_exec (db, sql);
sqlite3_free (sql);
return ret;
}
void
seaf_repo_manager_remove_account_repo (SeafRepoManager *mgr,
const char *repo_id,
const char *server,
const char *user)
{
GHashTable *repo_infos = NULL, *name_to_repo = NULL;
char *account_key = NULL;
RepoInfo *info = NULL, *copy = NULL;
SeafRepo *repo = NULL;
if (!seaf_repo_manager_account_exists (mgr, server, user)) {
return;
}
account_key = g_strconcat (server, "_", user, NULL);
pthread_rwlock_wrlock (&mgr->priv->account_lock);
repo_infos = mgr->priv->repo_infos;
name_to_repo = g_hash_table_lookup (mgr->priv->repo_names, account_key);
info = g_hash_table_lookup (repo_infos, repo_id);
if (info) {
copy = repo_info_copy (info);
g_hash_table_remove (repo_infos, repo_id);
g_hash_table_remove (name_to_repo, copy->display_name);
}
pthread_rwlock_unlock (&mgr->priv->account_lock);
pthread_mutex_lock (&mgr->priv->db_lock);
remove_repo_from_account (mgr->priv->db, server, user, repo_id);
pthread_mutex_unlock (&mgr->priv->db_lock);
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (repo) {
seaf_message ("Repo %s(%.8s) was deleted on server. Remove local repo.\n",
repo->name, repo->id);
seaf_repo_manager_mark_repo_deleted (seaf->repo_mgr, repo, TRUE);
http_tx_manager_cancel_task (seaf->http_tx_mgr, repo_id,
HTTP_TASK_TYPE_DOWNLOAD);
http_tx_manager_cancel_task (seaf->http_tx_mgr, repo_id,
HTTP_TASK_TYPE_UPLOAD);
seaf_repo_unref (repo);
}
g_free (account_key);
repo_info_free (copy);
return;
}
int
seaf_repo_manager_update_account_repos (SeafRepoManager *mgr,
const char *server,
const char *username,
GHashTable *server_repos,
GList **added,
GList **removed)
{
GHashTable *repo_infos, *name_to_repo;
GList *account_repos = NULL;
GHashTableIter iter;
gpointer key, value;
char *repo_id;
RepoInfo *info, *copy, *server_info;
GList *ptr;
char *display_name;
char *account_key = NULL;
SeafRepo *repo;
gboolean need_update;
GList *updated = NULL;
if (!seaf_repo_manager_account_exists (mgr, server, username)) {
return 0;
}
account_key = g_strconcat (server, "_", username, NULL);
pthread_rwlock_wrlock (&mgr->priv->account_lock);
repo_infos = mgr->priv->repo_infos;
name_to_repo = g_hash_table_lookup (mgr->priv->repo_names, account_key);
g_hash_table_iter_init (&iter, name_to_repo);
while (g_hash_table_iter_next (&iter, &key, &value)) {
info = value;
account_repos = g_list_prepend (account_repos, info);
}
for (ptr = account_repos; ptr; ptr = ptr->next) {
info = ptr->data;
repo_id = info->id;
server_info = g_hash_table_lookup (server_repos, repo_id);
if (!server_info) {
/* Remove local repos that don't exist in server_repos. */
copy = repo_info_copy (info);
*removed = g_list_prepend (*removed, copy);
g_hash_table_remove (repo_infos, repo_id);
g_hash_table_remove (name_to_repo, copy->display_name);
} else {
/* Update existing local repos. */
if (g_strcmp0 (info->head_commit_id, server_info->head_commit_id) != 0) {
g_free (info->head_commit_id);
info->head_commit_id = g_strdup(server_info->head_commit_id);
}
if (info->is_readonly != server_info->is_readonly) {
info->is_readonly = server_info->is_readonly;
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, info->id);
if (repo) {
if (info->is_readonly)
seaf_repo_set_readonly (repo);
else
seaf_repo_unset_readonly (repo);
seaf_repo_unref (repo);
}
}
need_update = FALSE;
if (info->type != server_info->type ||
g_strcmp0 (info->name, server_info->name) != 0) {
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, info->id);
if (repo && !repo->fs_ready) {
seaf_repo_unref (repo);
continue;
}
g_hash_table_remove (name_to_repo, info->name);
g_free (info->name);
info->name = g_strdup(server_info->name);
display_name = find_unique_display_name (name_to_repo,
server_info->type,
server_info->name);
info->display_name = display_name;
info->type = server_info->type;
g_hash_table_insert (name_to_repo, g_strdup(display_name), info);
if (repo) {
char *worktree = g_strdup (repo->worktree);
seaf_repo_set_worktree (repo, display_name);
g_free (worktree);
seaf_repo_unref (repo);
}
need_update = TRUE;
}
if (info->mtime != server_info->mtime) {
info->mtime = server_info->mtime;
need_update = TRUE;
}
if (need_update)
updated = g_list_append (updated, repo_info_copy (info));
if (server_info->is_corrupted)
info->is_corrupted = TRUE;
}
}
/* Add new repos from server_repos to local cache. */
g_hash_table_iter_init (&iter, server_repos);
while (g_hash_table_iter_next (&iter, &key, &value)) {
repo_id = key;
if (!g_hash_table_lookup (repo_infos, repo_id)) {
info = value;
copy = repo_info_copy (info);
*added = g_list_prepend (*added, copy);
}
}
for (ptr = *added; ptr; ptr = ptr->next) {
info = ptr->data;
display_name = find_unique_display_name (name_to_repo, info->type, info->name);
info->display_name = display_name;
copy = repo_info_copy (info);
g_hash_table_insert (repo_infos, g_strdup(info->id), copy);
g_hash_table_insert (name_to_repo, g_strdup(display_name), copy);
}
pthread_rwlock_unlock (&mgr->priv->account_lock);
pthread_mutex_lock (&mgr->priv->db_lock);
for (ptr = *removed; ptr; ptr = ptr->next) {
info = ptr->data;
remove_repo_from_account (mgr->priv->db, server, username, info->id);
}
for (ptr = *added; ptr; ptr = ptr->next) {
info = ptr->data;
add_repo_to_account (mgr->priv->db, server, username, info);
}
for (ptr = updated; ptr; ptr = ptr->next) {
info = ptr->data;
update_repo_to_account (mgr->priv->db, server, username, info);
}
pthread_mutex_unlock (&mgr->priv->db_lock);
g_free (account_key);
g_list_free (account_repos);
g_list_free_full (updated, (GDestroyNotify)repo_info_free);
return 0;
}
int
seaf_repo_manager_add_repo_to_account (SeafRepoManager *mgr,
const char *server,
const char *username,
SeafRepo *repo)
{
int ret = 0;
RepoInfo *info = NULL;
GHashTable *name_to_repo = NULL;
char *account_key = NULL;
if (!seaf_repo_manager_account_exists (mgr, server, username)) {
return -1;
}
account_key = g_strconcat (server, "_", username, NULL);
pthread_rwlock_wrlock (&mgr->priv->account_lock);
if (g_hash_table_lookup (mgr->priv->repo_infos, repo->id) != NULL) {
pthread_rwlock_unlock (&mgr->priv->account_lock);
goto out;
}
name_to_repo = g_hash_table_lookup (mgr->priv->repo_names, account_key);
info = repo_info_new (repo->id, repo->head->commit_id, repo->name,
repo->last_modify, FALSE);
info->type = REPO_TYPE_MINE;
info->display_name = find_unique_display_name (name_to_repo,
REPO_TYPE_MINE,
repo->name);
g_hash_table_replace (mgr->priv->repo_infos, g_strdup (info->id), info);
g_hash_table_replace (name_to_repo, g_strdup (info->display_name), info);
pthread_rwlock_unlock (&mgr->priv->account_lock);
pthread_mutex_lock (&mgr->priv->db_lock);
ret = add_repo_to_account (mgr->priv->db, server, username, info);
if (ret < 0) {
pthread_mutex_unlock (&mgr->priv->db_lock);
g_free (account_key);
repo_info_free (info);
return -1;
}
pthread_mutex_unlock (&mgr->priv->db_lock);
out:
g_free (account_key);
return ret;
}
int
seaf_repo_manager_rename_repo_on_account (SeafRepoManager *mgr,
const char *server,
const char *username,
const char *repo_id,
const char *new_name)
{
GHashTable *name_to_repo = NULL;
RepoInfo *info;
SeafRepo *repo;
char *orig_repo_name;
char *orig_display_name;
char *display_name;
char *account_key = NULL;
int ret = 0;
if (!seaf_repo_manager_account_exists (mgr, server, username)) {
ret = -1;
goto out;
}
account_key = g_strconcat (server, "_", username, NULL);
display_name = seaf_repo_manager_get_repo_display_name (mgr, repo_id);
pthread_rwlock_wrlock (&mgr->priv->account_lock);
name_to_repo = g_hash_table_lookup (mgr->priv->repo_names, account_key);
info = g_hash_table_lookup (name_to_repo, display_name);
g_free (display_name);
if (!info) {
ret = -1;
goto out;
}
g_hash_table_remove (name_to_repo, info->display_name);
orig_repo_name = info->name;
orig_display_name = info->display_name;
info->name = g_strdup (new_name);
info->display_name = find_unique_display_name (name_to_repo,
info->type,
new_name);
g_hash_table_replace (name_to_repo, g_strdup (info->display_name), info);
pthread_rwlock_unlock (&mgr->priv->account_lock);
pthread_mutex_lock (&mgr->priv->db_lock);
ret = update_repo_to_account (mgr->priv->db, server, username, info);
if (ret < 0) {
pthread_mutex_unlock (&mgr->priv->db_lock);
g_free (info->name);
g_free (info->display_name);
info->name = orig_repo_name;
info->display_name = orig_display_name;
g_hash_table_replace (name_to_repo,
g_strdup(info->display_name),
info);
goto out;
}
pthread_mutex_unlock (&mgr->priv->db_lock);
repo = seaf_repo_manager_get_repo (mgr, repo_id);
if (repo)
seaf_repo_set_worktree (repo, info->display_name);
seaf_repo_unref (repo);
g_free (orig_repo_name);
g_free (orig_display_name);
out:
g_free (account_key);
return ret;
}
void
seaf_repo_manager_set_repo_info_head_commit (SeafRepoManager *mgr,
const char *repo_id,
const char *commit_id)
{
RepoInfo *info;
pthread_rwlock_wrlock (&mgr->priv->account_lock);
info = g_hash_table_lookup (mgr->priv->repo_infos, repo_id);
if (!info)
goto out;
g_free (info->head_commit_id);
info->head_commit_id = g_strdup(commit_id);
out:
pthread_rwlock_unlock (&mgr->priv->account_lock);
}
#define JOURNAL_FLUSH_TIMEOUT 5
void
seaf_repo_manager_flush_account_repo_journals (SeafRepoManager *mgr,
const char *server,
const char *user)
{
GList *repo_ids, *ptr;
char *repo_id;
SeafRepo *repo;
repo_ids = seaf_repo_manager_get_account_repo_ids (seaf->repo_mgr, server, user);
for (ptr = repo_ids; ptr; ptr = ptr->next) {
repo_id = ptr->data;
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (repo) {
if (repo->journal)
journal_flush (repo->journal, JOURNAL_FLUSH_TIMEOUT);
seaf_repo_unref (repo);
}
}
g_list_free_full (repo_ids, g_free);
}
static gboolean
get_account_space_cb (sqlite3_stmt *stmt, void *data)
{
SeafAccountSpace *space = data;
gint64 total, used;
total = (gint64)sqlite3_column_int64 (stmt, 0);
used = (gint64)sqlite3_column_int64 (stmt, 1);
space->total = total;
space->used = used;
return FALSE;
}
#define DEFAULT_SPACE_USED (1000000000LL) /* 1GB */
#define DEFAULT_SPACE_TOTAL (100000000000000LL) /* 100TB */
#define MINIMAL_SPACE_TOTAL (10000000LL) /* 10MB */
#define UNLIMITED_SPACE (-2LL)
SeafAccountSpace *
seaf_repo_manager_get_account_space (SeafRepoManager *mgr,
const char *server,
const char *username)
{
char *sql;
SeafAccountSpace *space = g_new0 (SeafAccountSpace, 1);
sql = sqlite3_mprintf ("SELECT total, used FROM AccountSpace WHERE "
"server='%q' AND username='%q'",
server, username);
pthread_mutex_lock (&mgr->priv->db_lock);
if (sqlite_foreach_selected_row (mgr->priv->db, sql,
get_account_space_cb, space) <= 0) {
space->total = DEFAULT_SPACE_TOTAL;
space->used = DEFAULT_SPACE_USED;
}
pthread_mutex_unlock (&mgr->priv->db_lock);
if (space->total == UNLIMITED_SPACE)
space->total = (DEFAULT_SPACE_TOTAL > space->used ?
DEFAULT_SPACE_TOTAL : (10*space->used));
else if (space->total == 0)
/* If the user's quota is set to 0 on server, set it to a default quota so that
* the user can still write to shared libraries. Otherwise the OS may prevent
* writing data to the virtual drive.
*/
space->total = DEFAULT_SPACE_TOTAL;
sqlite3_free (sql);
return space;
}
int
seaf_repo_manager_set_account_space (SeafRepoManager *mgr,
const char *server, const char *username,
gint64 total, gint64 used)
{
char *sql;
int ret = 0;
sql = sqlite3_mprintf ("REPLACE INTO AccountSpace (server, username, total, used) "
"VALUES ('%q', '%q', %lld, %lld)",
server, username, total, used);
pthread_mutex_lock (&mgr->priv->db_lock);
ret = sqlite_query_exec (mgr->priv->db, sql);
pthread_mutex_unlock (&mgr->priv->db_lock);
sqlite3_free (sql);
return ret;
}
int
seaf_repo_manager_check_delete_repo (const char *repo_id, RepoType repo_type)
{
SeafRepo *repo = NULL;
int ret = 0;
if (repo_type != REPO_TYPE_MINE) {
seaf_warning ("rm repo: repo %s is not belong to the user.\n", repo_id);
ret = -EACCES;
goto out;
}
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo || !repo->fs_ready) {
ret = -ENOENT;
goto out;
}
if (!repo_tree_is_empty_repo(repo->tree)) {
ret = -ENOTEMPTY;
}
out:
seaf_repo_unref (repo);
return ret;
}
char *
seaf_repo_manager_get_display_name_by_repo_name (SeafRepoManager *mgr,
const char *server,
const char *user,
const char *repo_name)
{
GHashTable *name_to_repo = NULL;
char *account_key = NULL;
if (!seaf_repo_manager_account_exists (mgr, server, user)) {
return NULL;
}
account_key = g_strconcat (server, "_", user, NULL);
pthread_rwlock_rdlock (&mgr->priv->account_lock);
name_to_repo = g_hash_table_lookup (mgr->priv->repo_names, account_key);
pthread_rwlock_unlock (&mgr->priv->account_lock);
g_free (account_key);
return find_unique_display_name (name_to_repo,
REPO_TYPE_MINE,
repo_name);
}
static int
count_of_repo_id_in_account_repos (SeafRepoManager *mgr, const char *repo_id)
{
char sql[256];
gint64 ret;
sqlite3_snprintf (sizeof(sql), sql,
"SELECT count(*) AS count FROM AccountRepos WHERE repo_id = '%q'",
repo_id);
pthread_mutex_lock (&mgr->priv->db_lock);
ret = sqlite_get_int64 (mgr->priv->db, sql);
pthread_mutex_unlock (&mgr->priv->db_lock);
return ret;
}
char *
seaf_repo_manager_get_first_repo_token_from_account_repos (SeafRepoManager *mgr,
const char *server,
const char *username,
char **repo_token)
{
GHashTable *name_to_repo;
GHashTableIter iter;
gpointer key, value;
RepoInfo *info = NULL;
char *repo_id = NULL;
char *account_key = NULL;
SeafRepo *repo;
account_key = g_strconcat (server, "_", username, NULL);
name_to_repo = g_hash_table_lookup (mgr->priv->repo_names, account_key);
if (!name_to_repo) {
goto out;
}
g_hash_table_iter_init (&iter, name_to_repo);
while (g_hash_table_iter_next (&iter, &key, &value)) {
info = value;
repo = seaf_repo_manager_get_repo (mgr, info->id);
if (!repo || repo->delete_pending || !repo->token) {
seaf_repo_unref (repo);
continue;
}
//if the repo appears twice in AccountRepos, it may be shared
//to different users, skip it.
if (count_of_repo_id_in_account_repos (mgr, repo->id) >= 2) {
seaf_repo_unref (repo);
continue;
}
repo_id = g_strdup (repo->id);
*repo_token = g_strdup (repo->token);
seaf_repo_unref (repo);
break;
}
out:
g_free (account_key);
return repo_id;
}
int
seaf_repo_manager_set_repo_passwd (SeafRepoManager *manager,
SeafRepo *repo,
const char *passwd)
{
int ret;
if (seafile_decrypt_repo_enc_key (repo->enc_version, passwd, repo->random_key,
repo->salt,
repo->enc_key, repo->enc_iv) < 0)
return -1;
pthread_mutex_lock (&manager->priv->db_lock);
ret = save_repo_enc_info (manager, repo);
pthread_mutex_unlock (&manager->priv->db_lock);
if (ret != 0)
return ret;
repo->is_passwd_set = TRUE;
return ret;
}
int
seaf_repo_manager_clear_enc_repo_passwd (const char *repo_id)
{
SeafRepo *repo;
sqlite3 *db = seaf->repo_mgr->priv->db;
char sql[512];
sqlite3_snprintf(sizeof(sql), sql, "DELETE FROM RepoKeys WHERE repo_id = %Q",
repo_id);
pthread_mutex_lock (&seaf->repo_mgr->priv->db_lock);
if (sqlite_query_exec (db, sql) < 0) {
pthread_mutex_unlock (&seaf->repo_mgr->priv->db_lock);
return -1;
}
pthread_mutex_unlock (&seaf->repo_mgr->priv->db_lock);
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (repo) {
repo->is_passwd_set = FALSE;
memset (repo->enc_key, 0, 32);
memset (repo->enc_iv, 0, 16);
}
seaf_repo_unref (repo);
return 0;
}
json_t *
seaf_repo_manager_get_account_by_repo_id (SeafRepoManager *mgr, const char *repo_id)
{
json_t *object = NULL;
SeafRepo *repo = seaf_repo_manager_get_repo (mgr, repo_id);
if (!repo) {
return NULL;
}
object = json_object();
json_object_set (object, "server", json_string(repo->server));
json_object_set (object, "username", json_string(repo->user));
seaf_repo_unref (repo);
return object;
}
seadrive-fuse-3.0.13/src/repo-mgr.h 0000664 0000000 0000000 00000040405 14761776747 0017065 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#ifndef SEAF_REPO_MGR_H
#define SEAF_REPO_MGR_H
#include "common.h"
#include
#include
#include "commit-mgr.h"
#include "branch-mgr.h"
#include "repo-tree.h"
#include "journal-mgr.h"
#define REPO_PROP_TOKEN "token"
#define REPO_PROP_IS_READONLY "is-readonly"
struct _SeafRepoManager;
typedef struct _SeafRepo SeafRepo;
/* The caller can use the properties directly. But the caller should
* always write on repos via the API.
*/
struct _SeafRepo {
struct _SeafRepoManager *manager;
gchar id[37];
gchar *name;
gchar *desc;
gchar *category; /* not used yet */
gboolean encrypted;
gboolean is_passwd_set;
int enc_version;
gchar magic[65]; /* hash(repo_id + passwd), key stretched. */
gchar pwd_hash[65]; /* hash(repo_id + passwd), key stretched. */
gchar *pwd_hash_algo;
gchar *pwd_hash_params;
gchar random_key[97]; /* key length is 48 after encryption */
gchar salt[65];
gint64 last_modify;
SeafBranch *head;
gchar root_id[41];
gboolean is_corrupted;
gboolean delete_pending;
gboolean remove_cache;
int last_sync_time;
/* Last time check locked files. */
int last_check_locked_time;
gboolean checking_locked_files;
unsigned char enc_key[32]; /* 256-bit encryption key */
unsigned char enc_iv[16];
gchar *token; /* token for access this repo on server */
gchar *jwt_token;
gint64 last_check_jwt_token;
gboolean is_readonly;
// repo_uname is the same as display_name.
char *repo_uname;
char *worktree;
char *server;
char *user;
// use to connect notification server.
char *fileserver_addr;
unsigned int quota_full_notified : 1;
unsigned int access_denied_notified : 1;
int version;
gint refcnt;
/* Is this repo ready for file system operations. */
gboolean fs_ready;
/* In-memory representation of the repo directory hierarchy. */
struct _RepoTree *tree;
/* Journal for this repo */
Journal *journal;
/* Marks the repo into "partial-commit" mode. Once in this mode,
* we'll create commits for about every 100MB data. Commits are created
* and uploaded until all operations in the journal are processd.
*/
gboolean partial_commit_mode;
gboolean force_sync_pending;
};
gboolean is_repo_id_valid (const char *id);
SeafRepo*
seaf_repo_new (const char *id, const char *name, const char *desc);
void
seaf_repo_free (SeafRepo *repo);
void
seaf_repo_ref (SeafRepo *repo);
void
seaf_repo_unref (SeafRepo *repo);
int
seaf_repo_set_head (SeafRepo *repo, SeafBranch *branch);
SeafCommit *
seaf_repo_get_head_commit (const char *repo_id);
void
seaf_repo_set_readonly (SeafRepo *repo);
void
seaf_repo_unset_readonly (SeafRepo *repo);
void
seaf_repo_set_worktree (SeafRepo *repo, const char *repo_uname);
/* Update repo name, desc, magic etc from commit.
*/
void
seaf_repo_from_commit (SeafRepo *repo, SeafCommit *commit);
/* Update repo-related fields to commit.
*/
void
seaf_repo_to_commit (SeafRepo *repo, SeafCommit *commit);
int
seaf_repo_load_fs (SeafRepo *repo, gboolean after_clone);
GList *
seaf_repo_diff (SeafRepo *repo, const char *old, const char *new, int fold_dir_diff, char **error);
typedef struct _SeafRepoManager SeafRepoManager;
typedef struct _SeafRepoManagerPriv SeafRepoManagerPriv;
struct _SeafRepoManager {
struct _SeafileSession *seaf;
SeafRepoManagerPriv *priv;
};
SeafRepoManager*
seaf_repo_manager_new (struct _SeafileSession *seaf);
int
seaf_repo_manager_init (SeafRepoManager *mgr);
int
seaf_repo_manager_start (SeafRepoManager *mgr);
int
seaf_repo_manager_add_repo (SeafRepoManager *mgr, SeafRepo *repo);
int
seaf_repo_manager_mark_repo_deleted (SeafRepoManager *mgr, SeafRepo *repo, gboolean remove_cache);
int
seaf_repo_manager_del_repo (SeafRepoManager *mgr, SeafRepo *repo);
void
seaf_repo_manager_move_repo_store (SeafRepoManager *mgr,
const char *type,
const char *repo_id);
void
seaf_repo_manager_remove_repo_ondisk (SeafRepoManager *mgr, const char *repo_id, gboolean remove_cache);
SeafRepo*
seaf_repo_manager_get_repo (SeafRepoManager *manager, const gchar *id);
gboolean
seaf_repo_manager_repo_exists (SeafRepoManager *manager, const gchar *id);
GList*
seaf_repo_manager_get_repo_list (SeafRepoManager *mgr, int start, int limit);
GList *
seaf_repo_manager_get_enc_repo_list (SeafRepoManager *mgr, int start, int limit);
gboolean
seaf_repo_manager_is_repo_delete_pending (SeafRepoManager *manager, const char *id);
int
seaf_repo_manager_set_repo_token (SeafRepoManager *manager,
SeafRepo *repo,
const char *token);
int
seaf_repo_manager_remove_repo_token (SeafRepoManager *manager,
SeafRepo *repo);
void
seaf_repo_manager_rename_repo (SeafRepoManager *manager,
const char *repo_id,
const char *new_name);
int
seaf_repo_manager_branch_repo_unmap (SeafRepoManager *manager, SeafBranch *branch);
int
seaf_repo_manager_set_repo_property (SeafRepoManager *manager,
const char *repo_id,
const char *key,
const char *value);
char *
seaf_repo_manager_get_repo_property (SeafRepoManager *manager,
const char *repo_id,
const char *key);
/* Locked files. */
#define LOCKED_OP_UPDATE "update"
#define LOCKED_OP_DELETE "delete"
typedef struct _LockedFile {
char *operation;
gint64 old_mtime;
char file_id[41];
} LockedFile;
typedef struct _LockedFileSet {
SeafRepoManager *mgr;
char repo_id[37];
GHashTable *locked_files;
} LockedFileSet;
LockedFileSet *
seaf_repo_manager_get_locked_file_set (SeafRepoManager *mgr, const char *repo_id);
void
locked_file_set_free (LockedFileSet *fset);
int
locked_file_set_add_update (LockedFileSet *fset,
const char *path,
const char *operation,
gint64 old_mtime,
const char *file_id);
int
locked_file_set_remove (LockedFileSet *fset, const char *path, gboolean db_only);
LockedFile *
locked_file_set_lookup (LockedFileSet *fset, const char *path);
/* Folder Permissions. */
typedef enum FolderPermType {
FOLDER_PERM_TYPE_USER = 0,
FOLDER_PERM_TYPE_GROUP,
} FolderPermType;
typedef struct _FolderPerm {
char *path;
char *permission;
} FolderPerm;
void
folder_perm_free (FolderPerm *perm);
int
seaf_repo_manager_delete_folder_perm (SeafRepoManager *mgr,
const char *repo_id,
FolderPermType type,
FolderPerm *perm);
int
seaf_repo_manager_update_folder_perm (SeafRepoManager *mgr,
const char *repo_id,
FolderPermType type,
FolderPerm *perm);
int
seaf_repo_manager_update_folder_perms (SeafRepoManager *mgr,
const char *repo_id,
FolderPermType type,
GList *folder_perms);
int
seaf_repo_manager_update_folder_perm_timestamp (SeafRepoManager *mgr,
const char *repo_id,
gint64 timestamp);
gint64
seaf_repo_manager_get_folder_perm_timestamp (SeafRepoManager *mgr,
const char *repo_id);
gboolean
seaf_repo_manager_is_path_writable (SeafRepoManager *mgr,
const char *repo_id,
const char *path);
gboolean
seaf_repo_manager_is_path_invisible(SeafRepoManager *mgr,
const char *repo_id,
const char *path);
gboolean
seaf_repo_manager_include_invisible_perm (SeafRepoManager *mgr,
const char *repo_id);
/* Account repo list cache management. */
struct _SeafAccount {
char *server;
char *username;
char *nickname;
char *token;
char *name;
char *fileserver_addr;
gboolean is_pro;
char *unique_id; /* server + username */
gboolean repo_list_fetched;
gboolean all_repos_loaded;
gboolean server_disconnected;
};
typedef struct _SeafAccount SeafAccount;
void seaf_account_free (SeafAccount *account);
typedef struct AccountInfo {
char *server;
char *username;
} AccountInfo;
void
account_info_free (AccountInfo *info);
AccountInfo *
seaf_repo_manager_get_account_info_by_name (SeafRepoManager *mgr,
const char *name);
int
seaf_repo_manager_add_account (SeafRepoManager *mgr,
const char *server,
const char *username,
const char *nickname,
const char *token,
const char *name,
gboolean is_pro);
SeafAccount *
seaf_repo_manager_get_account (SeafRepoManager *mgr,
const char *server,
const char *username);
gboolean
seaf_repo_manager_account_exists (SeafRepoManager *mgr,
const char *server,
const char *username);
GList *
seaf_repo_manager_get_account_list (SeafRepoManager *mgr);
gboolean
seaf_repo_manager_account_is_pro (SeafRepoManager *mgr,
const char *server,
const char *user);
int
seaf_repo_manager_delete_account (SeafRepoManager *mgr,
const char *server,
const char *username,
gboolean remoce_cache,
GError **error);
typedef enum _RepoType {
REPO_TYPE_UNKNOWN = 0,
REPO_TYPE_MINE,
REPO_TYPE_SHARED,
REPO_TYPE_GROUP,
REPO_TYPE_PUBLIC,
N_REPO_TYPE,
} RepoType;
struct _RepoInfo {
char id[37];
char *head_commit_id; /* in-memory only. */
gboolean is_corrupted; /* in-memory only. */
gboolean is_readonly;
char *name;
/* Unique name to used in file system. */
char *display_name;
RepoType type;
gint64 mtime;
};
typedef struct _RepoInfo RepoInfo;
RepoInfo *
repo_info_new (const char *id, const char *head_commit_id,
const char *name, gint64 mtime, gboolean is_readonly);
RepoInfo *
repo_info_copy (RepoInfo *info);
void
repo_info_free (RepoInfo *info);
RepoType
repo_type_from_string (const char *type_str);
GList *
repo_type_string_list ();
GList *
seaf_repo_manager_get_account_repos (SeafRepoManager *mgr,
const char *server,
const char *user);
GList *
seaf_repo_manager_get_account_repo_ids (SeafRepoManager *mgr,
const char *server,
const char *user);
RepoInfo *
seaf_repo_manager_get_repo_info_by_display_name (SeafRepoManager *mgr,
const char *server,
const char *user,
const char *name);
char *
seaf_repo_manager_get_repo_id_by_display_name (SeafRepoManager *mgr,
const char *server,
const char *user,
const char *name);
char *
seaf_repo_manager_get_repo_display_name (SeafRepoManager *mgr,
const char *id);
int
seaf_repo_manager_update_account_repos (SeafRepoManager *mgr,
const char *server,
const char *username,
GHashTable *server_repos,
GList **added,
GList **removed);
void
seaf_repo_manager_remove_account_repo (SeafRepoManager *mgr,
const char *repo_id,
const char *server,
const char *user);
void
seaf_repo_manager_set_repo_info_head_commit (SeafRepoManager *mgr,
const char *repo_id,
const char *commit_id);
int
seaf_repo_manager_add_repo_to_account (SeafRepoManager *mgr,
const char *server,
const char *username,
SeafRepo *repo);
int
seaf_repo_manager_rename_repo_on_account (SeafRepoManager *mgr,
const char *server,
const char *username,
const char *repo_id,
const char *new_name);
void
seaf_repo_manager_set_repo_list_fetched (SeafRepoManager *mgr,
const char *server,
const char *user);
void
seaf_repo_manager_set_account_all_repos_loaded (SeafRepoManager *mgr,
const char *server,
const char *username);
void
seaf_repo_manager_flush_account_repo_journals (SeafRepoManager *mgr,
const char *server,
const char *username);
void
seaf_repo_manager_set_account_server_disconnected (SeafRepoManager *mgr,
const char *server,
const char *username,
gboolean server_disconnected);
struct _SeafAccountSpace {
gint64 total;
gint64 used;
};
typedef struct _SeafAccountSpace SeafAccountSpace;
SeafAccountSpace *
seaf_repo_manager_get_account_space (SeafRepoManager *mgr,
const char *server,
const char *username);
int
seaf_repo_manager_set_account_space (SeafRepoManager *mgr,
const char *server, const char *username,
gint64 total, gint64 used);
int
seaf_repo_manager_check_delete_repo (const char *repo_id, RepoType repo_type);
int
seaf_repo_manager_create_placeholders (SeafRepo* repo);
char *
seaf_repo_manager_get_display_name_by_repo_name (SeafRepoManager *mgr,
const char *server,
const char *user,
const char *repo_name);
void
seaf_repo_manager_create_dir_placeholder_recursive (const char *repo_id, const char *dir_id, const char *fullpath, const char *path);
gboolean
seaf_repo_manager_is_deleted_repo (SeafRepoManager *mgr, const char *display_name);
void
seaf_repo_manager_del_deleted_repo (SeafRepoManager *mgr, const char *display_name);
int
seaf_repo_manager_set_repo_passwd (SeafRepoManager *manager,
SeafRepo *repo,
const char *passwd);
int
seaf_repo_manager_clear_enc_repo_passwd (const char *repo_id);
char *
seaf_repo_manager_get_first_repo_token_from_account_repos (SeafRepoManager *mgr,
const char *server,
const char *username,
char **repo_token);
json_t *
seaf_repo_manager_get_account_by_repo_id (SeafRepoManager *mgr, const char *repo_id);
#endif
seadrive-fuse-3.0.13/src/repo-tree.c 0000664 0000000 0000000 00000062163 14761776747 0017237 0 ustar 00root root 0000000 0000000 #include "common.h"
#include
#include
#include "repo-tree.h"
#include "log.h"
#include "utils.h"
#include "seafile-session.h"
struct _RepoTreeDir;
typedef struct _RepoTreeDir RepoTreeDir;
struct _RepoTreeDirent;
typedef struct _RepoTreeDirent RepoTreeDirent;
struct _RepoTree {
char repo_id[37];
RepoTreeDir *root;
pthread_rwlock_t lock;
};
struct _RepoTreeDir {
char id[41];
GHashTable *dirents;
};
struct _RepoTreeDirent {
char id[41];
char *name;
guint32 mode;
gint64 mtime;
gint64 size;
RepoTreeDir *subdir;
};
static RepoTreeDir *
repo_tree_dir_new (const char *id);
static void
repo_tree_dir_free (RepoTreeDir *dir);
static RepoTreeDirent *
repo_tree_dirent_new (const char *id,
const char *name,
guint32 mode,
gint64 mtime,
gint64 size,
RepoTreeDir *subdir)
{
RepoTreeDirent *dirent = g_new0 (RepoTreeDirent, 1);
memcpy (dirent->id, id, 40);
dirent->name = g_strdup(name);
dirent->mode = mode;
dirent->mtime = mtime;
dirent->size = size;
if (S_ISDIR(mode)) {
if (!subdir)
subdir = repo_tree_dir_new (EMPTY_SHA1);
dirent->subdir = subdir;
}
return dirent;
}
static void
repo_tree_dirent_free (RepoTreeDirent *dirent)
{
if (!dirent)
return;
g_free (dirent->name);
repo_tree_dir_free (dirent->subdir);
g_free (dirent);
}
static RepoTreeDir *
repo_tree_dir_new (const char *id)
{
RepoTreeDir *dir = g_new0 (RepoTreeDir, 1);
memcpy (dir->id, id, 40);
dir->dirents = g_hash_table_new_full (g_str_hash, g_str_equal,
NULL, (GDestroyNotify)repo_tree_dirent_free);
return dir;
}
static void
repo_tree_dir_free (RepoTreeDir *dir)
{
if (!dir)
return;
g_hash_table_destroy (dir->dirents);
g_free (dir);
}
static gboolean
repo_tree_dir_is_empty (RepoTreeDir *dir)
{
return (g_hash_table_size(dir->dirents) == 0);
}
RepoTree *
repo_tree_new (const char *repo_id)
{
RepoTree *tree = NULL;
tree = g_new0 (RepoTree, 1);
memcpy (tree->repo_id, repo_id, 36);
pthread_rwlock_init (&tree->lock, NULL);
return tree;
}
void
repo_tree_free (RepoTree *tree)
{
if (!tree)
return;
repo_tree_dir_free (tree->root);
pthread_rwlock_destroy (&tree->lock);
g_free (tree);
}
static RepoTreeDir *
load_dir_recursive (const char *repo_id, const char *dir_id);
int
repo_tree_load_commit (RepoTree *tree, const char *commit_id)
{
SeafCommit *commit;
RepoTreeDir *root;
int ret = 0;
commit = seaf_commit_manager_get_commit (seaf->commit_mgr,
tree->repo_id, 1,
commit_id);
if (!commit) {
seaf_warning ("Failed to get commit %s from repo %s.\n",
commit_id, tree->repo_id);
return -1;
}
root = load_dir_recursive (tree->repo_id, commit->root_id);
if (!root) {
ret = -1;
goto out;
}
pthread_rwlock_wrlock (&tree->lock);
repo_tree_dir_free (tree->root);
tree->root = root;
pthread_rwlock_unlock (&tree->lock);
out:
seaf_commit_unref (commit);
return ret;
}
static RepoTreeDir *
load_dir_recursive (const char *repo_id, const char *dir_id)
{
SeafDir *seafdir;
GList *ptr;
SeafDirent *seafdirent;
RepoTreeDirent *dirent;
RepoTreeDir *subdir;
RepoTreeDir *dir = NULL;
seafdir = seaf_fs_manager_get_seafdir (seaf->fs_mgr, repo_id, 1, dir_id);
if (!seafdir) {
seaf_warning ("Failed to get dir %s from repo %s.\n", dir_id, repo_id);
return NULL;
}
dir = repo_tree_dir_new (dir_id);
gboolean error = FALSE;
for (ptr = seafdir->entries; ptr; ptr = ptr->next) {
seafdirent = (SeafDirent *)ptr->data;
if (seaf_sync_manager_ignored_on_checkout (seafdirent->name, NULL))
continue;
if (S_ISDIR(seafdirent->mode)) {
subdir = load_dir_recursive (repo_id, seafdirent->id);
if (!subdir) {
seaf_warning ("Failed to get dir %s from repo %s.\n",
seafdirent->id, repo_id);
error = TRUE;
break;
}
} else {
subdir = NULL;
}
dirent = repo_tree_dirent_new (seafdirent->id, seafdirent->name,
seafdirent->mode, seafdirent->mtime,
seafdirent->size, subdir);
g_hash_table_insert (dir->dirents, dirent->name, dirent);
}
if (error) {
repo_tree_dir_free (dir);
dir = NULL;
}
seaf_dir_free (seafdir);
return dir;
}
void
repo_tree_clear (RepoTree *tree)
{
pthread_rwlock_wrlock (&tree->lock);
repo_tree_dir_free (tree->root);
tree->root = NULL;
pthread_rwlock_unlock (&tree->lock);
}
gboolean
repo_tree_is_loaded (RepoTree *tree)
{
gboolean ret = FALSE;
pthread_rwlock_rdlock (&tree->lock);
ret = (tree->root != NULL);
pthread_rwlock_unlock (&tree->lock);
return ret;
}
static RepoTreeDirent *
resolve_path (RepoTree *tree, const char *path)
{
char **comps = NULL;
char *comp;
guint i, n;
RepoTreeDir *dir;
RepoTreeDirent *dirent = NULL;
comps = g_strsplit (path, "/", -1);
n = g_strv_length (comps);
if (!comps || n == 0) {
g_strfreev (comps);
return NULL;
}
dir = tree->root;
for (i = 0; i < n; ++i) {
comp = comps[i];
dirent = g_hash_table_lookup (dir->dirents, comp);
if (!dirent)
break;
if (S_ISDIR(dirent->mode)) {
dir = dirent->subdir;
} else {
if (i < n - 1) {
dirent = NULL;
}
break;
}
}
g_strfreev (comps);
return dirent;
}
int
repo_tree_stat_path (RepoTree *tree, const char *path, RepoTreeStat *st)
{
if (path[0] == '\0')
return -ENOENT;
pthread_rwlock_rdlock (&tree->lock);
if (!tree->root) {
pthread_rwlock_unlock (&tree->lock);
return -ENOENT;
}
RepoTreeDirent *dirent = resolve_path (tree, path);
if (!dirent) {
pthread_rwlock_unlock (&tree->lock);
return -ENOENT;
}
memcpy (st->id, dirent->id, sizeof (st->id));
st->mode = dirent->mode;
st->size = dirent->size;
st->mtime = dirent->mtime;
pthread_rwlock_unlock (&tree->lock);
return 0;
}
int
repo_tree_readdir (RepoTree *tree, const char *path, GHashTable *dirents)
{
RepoTreeDirent *dirent;
RepoTreeDir *dir;
GHashTableIter iter;
gpointer key, value;
RepoTreeStat *st;
int ret = 0;
pthread_rwlock_rdlock (&tree->lock);
if (!tree->root) {
ret = -ENOENT;
goto out;
}
if (path[0] == '\0') {
dir = tree->root;
} else {
dirent = resolve_path (tree, path);
if (!dirent) {
ret = -ENOENT;
goto out;
}
if (!S_ISDIR(dirent->mode)) {
ret = -ENOTDIR;
goto out;
}
dir = dirent->subdir;
}
char *sub_path = NULL;
g_hash_table_iter_init (&iter, dir->dirents);
while (g_hash_table_iter_next (&iter, &key, &value)) {
dirent = (RepoTreeDirent *)value;
st = g_new0 (RepoTreeStat, 1);
st->mode = dirent->mode;
st->size = dirent->size;
st->mtime = dirent->mtime;
sub_path = g_build_path ("/", path, dirent->name, NULL);
if (seaf_repo_manager_is_path_invisible (seaf->repo_mgr, tree->repo_id, sub_path)) {
g_free (sub_path);
continue;
}
g_free (sub_path);
g_hash_table_insert (dirents, g_strdup(dirent->name), st);
}
out:
pthread_rwlock_unlock (&tree->lock);
return ret;
}
static int
repo_tree_create_common (RepoTree *tree, const char *path, const char *id,
guint32 mode, gint64 mtime, gint64 size)
{
char *parent = NULL, *dname = NULL;
RepoTreeDirent *dirent;
RepoTreeDir *dir, *empty_dir = NULL;
int ret = 0;
pthread_rwlock_wrlock (&tree->lock);
if (!tree->root) {
ret = -ENOENT;
goto out;
}
if (path[0] == '\0') {
ret = -EINVAL;
goto out;
}
parent = g_path_get_dirname (path);
dname = g_path_get_basename (path);
if (strcmp (parent, ".") == 0) {
dir = tree->root;
} else {
dirent = resolve_path (tree, parent);
if (!dirent) {
ret = -ENOENT;
goto out;
}
dir = dirent->subdir;
}
if (g_hash_table_lookup (dir->dirents, dname) != NULL) {
ret = -EEXIST;
goto out;
}
if (S_ISDIR(mode)) {
empty_dir = repo_tree_dir_new (EMPTY_SHA1);
}
dirent = repo_tree_dirent_new (id,
dname,
create_mode(mode),
mtime,
size,
empty_dir);
g_hash_table_insert (dir->dirents, dirent->name, dirent);
out:
g_free (parent);
g_free (dname);
pthread_rwlock_unlock (&tree->lock);
return ret;
}
int
repo_tree_create_file (RepoTree *tree, const char *path, const char *id,
guint32 mode, gint64 mtime, gint64 size)
{
return repo_tree_create_common (tree, path, id, mode, mtime, size);
}
int
repo_tree_mkdir (RepoTree *tree, const char *path, gint64 mtime)
{
return repo_tree_create_common (tree, path, EMPTY_SHA1, S_IFDIR, mtime, 0);
}
static int
repo_tree_remove_common (RepoTree *tree, const char *path, gboolean is_dir)
{
char *parent = NULL, *dname = NULL;
RepoTreeDirent *dirent;
RepoTreeDir *dir;
int ret = 0;
pthread_rwlock_wrlock (&tree->lock);
if (!tree->root) {
ret = -ENOENT;
goto out;
}
if (path[0] == '\0') {
ret = -EINVAL;
goto out;
}
parent = g_path_get_dirname (path);
dname = g_path_get_basename (path);
if (strcmp (parent, ".") == 0) {
dir = tree->root;
} else {
dirent = resolve_path (tree, parent);
if (!dirent) {
ret = -ENOENT;
goto out;
}
dir = dirent->subdir;
}
dirent = g_hash_table_lookup (dir->dirents, dname);
if (!dirent) {
ret = -ENOENT;
goto out;
}
if (is_dir && !S_ISDIR(dirent->mode)) {
ret = -ENOTDIR;
goto out;
} else if (!is_dir && S_ISDIR(dirent->mode)) {
ret = -EISDIR;
goto out;
}
if (S_ISDIR(dirent->mode) && !repo_tree_dir_is_empty(dirent->subdir)) {
ret = -ENOTEMPTY;
goto out;
}
g_hash_table_remove (dir->dirents, dname);
out:
g_free (parent);
g_free (dname);
pthread_rwlock_unlock (&tree->lock);
return ret;
}
int
repo_tree_unlink (RepoTree *tree, const char *path)
{
return repo_tree_remove_common (tree, path, FALSE);
}
int
repo_tree_rmdir (RepoTree *tree, const char *path)
{
return repo_tree_remove_common (tree, path, TRUE);
}
int
repo_tree_rename (RepoTree *tree, const char *oldpath, const char *newpath,
gboolean replace_existing)
{
char *parent1 = NULL, *dname1 = NULL;
char *parent2 = NULL, *dname2 = NULL;
RepoTreeDirent *dirent, *dirent1, *dirent2, *new_dirent;
RepoTreeDir *dir1, *dir2;
int ret = 0;
pthread_rwlock_wrlock (&tree->lock);
if (!tree->root) {
ret = -ENOENT;
goto out;
}
if (oldpath[0] == '\0' || newpath[0] == '\0') {
ret = -EINVAL;
goto out;
}
parent1 = g_path_get_dirname (oldpath);
dname1 = g_path_get_basename (oldpath);
if (strcmp (parent1, ".") == 0) {
dir1 = tree->root;
} else {
dirent = resolve_path (tree, parent1);
if (!dirent) {
ret = -ENOENT;
goto out;
}
dir1 = dirent->subdir;
}
dirent1 = g_hash_table_lookup (dir1->dirents, dname1);
if (!dirent1) {
ret = -ENOENT;
goto out;
}
parent2 = g_path_get_dirname (newpath);
dname2 = g_path_get_basename (newpath);
if (strcmp (parent2, ".") == 0) {
dir2 = tree->root;
} else {
dirent = resolve_path (tree, parent2);
if (!dirent) {
ret = -ENOENT;
goto out;
}
dir2 = dirent->subdir;
}
dirent2 = g_hash_table_lookup (dir2->dirents, dname2);
if (dirent2) {
if (!replace_existing) {
ret = -EEXIST;
goto out;
}
if (S_ISDIR(dirent2->mode) && !S_ISDIR(dirent1->mode)) {
ret = -EISDIR;
goto out;
}
if (S_ISDIR(dirent1->mode) && !S_ISDIR(dirent2->mode)) {
ret = -ENOTDIR;
goto out;
}
if (S_ISDIR(dirent1->mode) && S_ISDIR(dirent2->mode) &&
!repo_tree_dir_is_empty (dirent2->subdir)) {
ret = -ENOTEMPTY;
goto out;
}
}
/* OK, all checks are passed. Replace dirent2 with dirent1. */
/* Remove dirent1 from dir1 but don't free it. */
g_hash_table_steal (dir1->dirents, dname1);
new_dirent = dirent1;
g_free (new_dirent->name);
new_dirent->name = g_strdup(dname2);
g_hash_table_replace (dir2->dirents, new_dirent->name, new_dirent);
out:
g_free (parent1);
g_free (dname1);
g_free (parent2);
g_free (dname2);
pthread_rwlock_unlock (&tree->lock);
return ret;
}
int
repo_tree_add_subtree (RepoTree *tree, const char *path, const char *root_id, gint64 mtime)
{
char *parent = NULL, *dname = NULL;
RepoTreeDirent *dirent;
RepoTreeDir *dir, *subdir = NULL;
int ret = 0;
subdir = load_dir_recursive (tree->repo_id, root_id);
if (!subdir) {
seaf_warning ("Failed to load repo dir %s of repo %s.\n",
root_id, tree->repo_id);
return -1;
}
pthread_rwlock_wrlock (&tree->lock);
if (!tree->root) {
ret = -ENOENT;
goto out;
}
if (path[0] == '\0') {
ret = -EINVAL;
goto out;
}
parent = g_path_get_dirname (path);
dname = g_path_get_basename (path);
if (strcmp (parent, ".") == 0) {
dir = tree->root;
} else {
dirent = resolve_path (tree, parent);
if (!dirent) {
ret = -ENOENT;
goto out;
}
dir = dirent->subdir;
}
if (g_hash_table_lookup (dir->dirents, dname) != NULL) {
ret = -EEXIST;
goto out;
}
dirent = repo_tree_dirent_new (root_id,
dname,
create_mode(S_IFDIR),
mtime,
0,
subdir);
g_hash_table_insert (dir->dirents, dirent->name, dirent);
out:
g_free (parent);
g_free (dname);
if (ret < 0)
repo_tree_dir_free (subdir);
pthread_rwlock_unlock (&tree->lock);
return ret;
}
static int
remove_dir_recursive (const char *repo_id, RepoTreeDir *dir, const char *path)
{
GHashTableIter iter;
gpointer key, value;
RepoTreeDirent *dirent;
char *sub_path;
g_hash_table_iter_init (&iter, dir->dirents);
while (g_hash_table_iter_next (&iter, &key, &value)) {
dirent = (RepoTreeDirent *)value;
sub_path = g_strconcat (path, "/", dirent->name, NULL);
if (S_ISREG(dirent->mode)) {
if (file_cache_mgr_is_file_changed (seaf->file_cache_mgr,
repo_id, sub_path,
TRUE)) {
g_free (sub_path);
continue;
}
g_hash_table_iter_remove (&iter);
file_cache_mgr_unlink (seaf->file_cache_mgr, repo_id, sub_path);
} else {
if (remove_dir_recursive (repo_id, dirent->subdir, sub_path) == 0) {
g_hash_table_iter_remove (&iter);
file_cache_mgr_rmdir (seaf->file_cache_mgr, repo_id, sub_path);
}
}
g_free (sub_path);
}
int ret = g_hash_table_size(dir->dirents);
return ret;
}
int
repo_tree_remove_subtree (RepoTree *tree, const char *path)
{
char *parent = NULL, *dname = NULL;
RepoTreeDirent *dirent;
RepoTreeDir *dir;
int ret = 0;
pthread_rwlock_wrlock (&tree->lock);
if (!tree->root) {
ret = -ENOENT;
goto out;
}
if (path[0] == '\0') {
ret = -EINVAL;
goto out;
}
parent = g_path_get_dirname (path);
dname = g_path_get_basename (path);
if (strcmp (parent, ".") == 0) {
dir = tree->root;
} else {
dirent = resolve_path (tree, parent);
if (!dirent) {
ret = -ENOENT;
goto out;
}
dir = dirent->subdir;
}
dirent = g_hash_table_lookup (dir->dirents, dname);
if (!dirent) {
goto out;
}
if (remove_dir_recursive (tree->repo_id, dirent->subdir, path) == 0) {
g_hash_table_remove (dir->dirents, dname);
file_cache_mgr_rmdir (seaf->file_cache_mgr, tree->repo_id, path);
}
out:
g_free (parent);
g_free (dname);
pthread_rwlock_unlock (&tree->lock);
return ret;
}
int
repo_tree_set_file_size (RepoTree *tree, const char *path, gint64 size)
{
RepoTreeDirent *dirent;
pthread_rwlock_wrlock (&tree->lock);
dirent = resolve_path (tree, path);
if (!dirent || !S_ISREG(dirent->mode)) {
pthread_rwlock_unlock (&tree->lock);
return -1;
}
dirent->size = size;
pthread_rwlock_unlock (&tree->lock);
return 0;
}
int
repo_tree_set_file_mtime (RepoTree *tree, const char *path, gint64 mtime)
{
RepoTreeDirent *dirent;
pthread_rwlock_wrlock (&tree->lock);
dirent = resolve_path (tree, path);
if (!dirent || !S_ISREG(dirent->mode)) {
pthread_rwlock_unlock (&tree->lock);
return -1;
}
dirent->mtime = mtime;
pthread_rwlock_unlock (&tree->lock);
return 0;
}
int
repo_tree_set_file_id (RepoTree *tree, const char *path, const char *new_id)
{
RepoTreeDirent *dirent;
pthread_rwlock_wrlock (&tree->lock);
dirent = resolve_path (tree, path);
if (!dirent || !S_ISREG(dirent->mode)) {
pthread_rwlock_unlock (&tree->lock);
return -1;
}
memcpy (dirent->id, new_id, 40);
pthread_rwlock_unlock (&tree->lock);
return 0;
}
gboolean
repo_tree_is_empty_repo (RepoTree *tree)
{
GHashTable *dirents = tree->root->dirents;
return g_hash_table_size(dirents) == 0 ? TRUE:FALSE;
}
gboolean
repo_tree_is_empty_dir (RepoTree *tree, const char *path)
{
RepoTreeDirent *dirent;
RepoTreeDir *dir;
gboolean ret;
pthread_rwlock_rdlock (&tree->lock);
if (!tree->root) {
pthread_rwlock_unlock (&tree->lock);
return FALSE;
}
if (path[0] == '\0') {
dir = tree->root;
} else {
dirent = resolve_path (tree, path);
if (!dirent) {
pthread_rwlock_unlock (&tree->lock);
return FALSE;
}
dir = dirent->subdir;
}
if (!dir) {
ret = FALSE;
goto out;
}
ret = repo_tree_dir_is_empty (dir);
out:
pthread_rwlock_unlock (&tree->lock);
return ret;
}
static void
traverse_recursive (RepoTree *tree, RepoTreeDir *dir, const char *path,
RepoTreeTraverseCallback callback)
{
GHashTableIter iter;
gpointer key, value;
RepoTreeStat st;
char *dname;
char *subpath;
RepoTreeDirent *dirent;
g_hash_table_iter_init (&iter, dir->dirents);
while (g_hash_table_iter_next (&iter, &key, &value)) {
dname = (char *)key;
dirent = (RepoTreeDirent *)value;
subpath = g_build_path ("/", path, dname, NULL);
if (S_ISREG(dirent->mode)) {
memset (&st, 0, sizeof(st));
memcpy (st.id, dirent->id, 40);
st.mode = dirent->mode;
st.size = dirent->size;
st.mtime = dirent->mtime;
callback (tree->repo_id, subpath, &st);
} else {
memset (&st, 0, sizeof(st));
memcpy (st.id, dirent->id, 40);
st.mode = dirent->mode;
callback (tree->repo_id, subpath, &st);
traverse_recursive (tree, dirent->subdir, subpath, callback);
}
g_free (subpath);
}
}
int
repo_tree_traverse (RepoTree *tree,
const char *root_path,
RepoTreeTraverseCallback callback)
{
RepoTreeDirent *dirent;
RepoTreeDir *dir;
int ret = 0;
RepoTreeStat st;
pthread_rwlock_rdlock (&tree->lock);
if (!tree->root) {
ret = -ENOENT;
goto out;
}
if (root_path[0] == '\0') {
dir = tree->root;
} else {
dirent = resolve_path (tree, root_path);
if (!dirent) {
ret = -ENOENT;
goto out;
}
if (!S_ISDIR(dirent->mode)) {
memset (&st, 0, sizeof(st));
memcpy (st.id, dirent->id, 40);
st.mode = dirent->mode;
st.size = dirent->size;
st.mtime = dirent->mtime;
callback (tree->repo_id, root_path, &st);
goto out;
} else {
memset (&st, 0, sizeof(st));
memcpy (st.id, dirent->id, 40);
st.mode = dirent->mode;
callback (tree->repo_id, root_path, &st);
dir = dirent->subdir;
}
}
traverse_recursive (tree, dir, root_path, callback);
out:
pthread_rwlock_unlock (&tree->lock);
return ret;
}
static gboolean
find_office_file_path (RepoTree *tree,
const char *parent_dir,
const char *lock_file_name,
gboolean is_wps,
char **office_path)
{
GHashTable *dirents = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
GHashTableIter iter;
gpointer key, value;
char *dname, *dname_nohead;
gboolean ret = FALSE;
if (repo_tree_readdir (tree, parent_dir, dirents) < 0) {
g_hash_table_destroy (dirents);
return FALSE;
}
g_hash_table_iter_init (&iter, dirents);
while (g_hash_table_iter_next (&iter, &key, &value)) {
dname = (char *)key;
if (strlen(dname) < 2 || strncmp(dname, "~$", 2) == 0 || strncmp(dname, ".~", 2) == 0) {
continue;
}
if (is_wps) {
dname_nohead = g_utf8_next_char(dname);
// WPS may replace the first one or two characters of the filename with ".~" based on the length of the filename.
if (strcmp (dname_nohead, lock_file_name) == 0) {
*office_path = g_build_path("/", parent_dir, dname, NULL);
ret = TRUE;
break;
}
dname_nohead = g_utf8_next_char(dname_nohead);
} else {
dname_nohead = g_utf8_next_char(g_utf8_next_char(dname));
}
if (strcmp (dname_nohead, lock_file_name) == 0) {
*office_path = g_build_path("/", parent_dir, dname, NULL);
ret = TRUE;
break;
}
}
g_hash_table_destroy (dirents);
return ret;
}
gboolean
repo_tree_is_office_lock_file (RepoTree *tree, const char *path, char **office_path)
{
gboolean ret;
gboolean is_wps = FALSE;
if (!seaf->enable_auto_lock || !seaf->office_lock_file_regex ||
!seaf->libre_office_lock_file_regex || !seaf->wps_lock_file_regex)
return FALSE;
if (g_regex_match (seaf->office_lock_file_regex, path, 0, NULL))
/* Replace ~$abc.docx with abc.docx */
*office_path = g_regex_replace (seaf->office_lock_file_regex,
path, -1, 0,
"\\1", 0, NULL);
else if (g_regex_match (seaf->libre_office_lock_file_regex, path, 0, NULL))
/* Replace .~lock.abc.docx# with abc.docx */
*office_path = g_regex_replace (seaf->libre_office_lock_file_regex,
path, -1, 0,
"\\1", 0, NULL);
else if (g_regex_match (seaf->wps_lock_file_regex, path, 0, NULL)) {
/* Replace .~abc.docx with abc.docx */
*office_path = g_regex_replace (seaf->wps_lock_file_regex,
path, -1, 0,
"\\1", 0, NULL);
is_wps = TRUE;
} else
return FALSE;
/* When the filename is long, sometimes the first two characters
in the filename will be directly replaced with ~$.
So if the office_path file doesn't exist, we have to match
against all filenames in this directory, to find the office
file's name.
*/
RepoTreeStat st;
if (repo_tree_stat_path (tree, *office_path, &st) == 0) {
return TRUE;
}
char *lock_file_name = g_path_get_basename(*office_path);
char *parent_dir = g_path_get_dirname(*office_path);
if (strcmp(parent_dir, ".") == 0) {
g_free (parent_dir);
parent_dir = g_strdup("");
}
g_free (*office_path);
*office_path = NULL;
ret = find_office_file_path (tree, parent_dir, lock_file_name, is_wps, office_path);
g_free (lock_file_name);
g_free (parent_dir);
return ret;
}
seadrive-fuse-3.0.13/src/repo-tree.h 0000664 0000000 0000000 00000004725 14761776747 0017244 0 ustar 00root root 0000000 0000000 #ifndef SEAF_REPO_TREE_H
#define SEAF_REPO_TREE_H
#include
/*
* RepoTree is a in-memory representation of the directory hierarchy of a repo.
*/
struct _RepoTree;
typedef struct _RepoTree RepoTree;
RepoTree *
repo_tree_new (const char *repo_id);
int
repo_tree_load_commit (RepoTree *tree, const char *commit_id);
/* Clear the contents of the tree, but not free it. */
void
repo_tree_clear (RepoTree *tree);
void
repo_tree_free (RepoTree *tree);
gboolean
repo_tree_is_loaded (RepoTree *tree);
struct _RepoTreeStat {
/* Id is only valid for files. The ID of dirs are not always correct. */
char id[41];
guint32 mode;
gint64 size;
gint64 mtime;
};
typedef struct _RepoTreeStat RepoTreeStat;
int
repo_tree_stat_path (RepoTree *tree, const char *path, RepoTreeStat *st);
int
repo_tree_readdir (RepoTree *tree, const char *path, GHashTable *dirents);
int
repo_tree_create_file (RepoTree *tree, const char *path, const char *id,
guint32 mode, gint64 mtime, gint64 size);
int
repo_tree_mkdir (RepoTree *tree, const char *path, gint64 mtime);
int
repo_tree_unlink (RepoTree *tree, const char *path);
int
repo_tree_rmdir (RepoTree *tree, const char *path);
/* Rename @oldpath from @tree to @newpath.
*/
int
repo_tree_rename (RepoTree *tree, const char *oldpath, const char *newpath,
gboolean replace_existing);
/* Load the subtree pointed to by @root_id and add this subtree to @path. */
int
repo_tree_add_subtree (RepoTree *tree, const char *path, const char *root_id, gint64 mtime);
/* Recursively remove a subtree on @path */
int
repo_tree_remove_subtree (RepoTree *tree, const char *path);
int
repo_tree_set_file_mtime (RepoTree *tree, const char *path,
gint64 mtime);
int
repo_tree_set_file_size (RepoTree *tree, const char *path,
gint64 size);
int
repo_tree_set_file_id (RepoTree *tree, const char *path, const char *new_id);
gboolean
repo_tree_is_empty_repo (RepoTree *tree);
gboolean
repo_tree_is_empty_dir (RepoTree *tree, const char *path);
typedef void (*RepoTreeTraverseCallback) (const char * repo_id,
const char *path,
RepoTreeStat *st);
int
repo_tree_traverse (RepoTree *tree,
const char *root_path,
RepoTreeTraverseCallback callback);
gboolean
repo_tree_is_office_lock_file (RepoTree *tree, const char *path, char **office_path);
#endif
seadrive-fuse-3.0.13/src/rpc-service.c 0000664 0000000 0000000 00000071056 14761776747 0017560 0 ustar 00root root 0000000 0000000 #include "common.h"
#include
#include
#include "searpc-signature.h"
#include "searpc-marshal.h"
#include
#include
#include "repo-mgr.h"
#include "seafile-session.h"
#include "seafile-config.h"
#include "seafile-crypt.h"
#include "seafile-error.h"
#include "utils.h"
#include "log.h"
static int
seafile_add_account (const char *server, const char *username,
const char *nickname,
const char *token,
const char *name,
int is_pro, GError **error)
{
if (!server || !username || !token
|| !name
) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Empty Arguments");
return -1;
}
return seaf_repo_manager_add_account (seaf->repo_mgr,
server,
username,
nickname,
token,
name,
(is_pro > 0));
}
/* static int */
/* seafile_update_account (const char *server, const char *username, const char *token, */
/* GError **error) */
/* { */
/* if (!server || !username || !token) { */
/* g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Empty Arguments"); */
/* return -1; */
/* } */
/* return seaf_repo_manager_update_current_account (seaf->repo_mgr, */
/* server, */
/* username, */
/* token); */
/* } */
static int
seafile_delete_account (const char *server,
const char *username,
gboolean remove_cache,
GError **error)
{
if (!server || !username
) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Empty Arguments");
return -1;
}
return seaf_repo_manager_delete_account (seaf->repo_mgr,
server,
username,
remove_cache,
error);
}
static int
seafile_set_config (const char *key, const char *value, GError **error)
{
return seafile_session_config_set_string(seaf, key, value);
}
static char *
seafile_get_config (const char *key, GError **error)
{
return seafile_session_config_get_string(seaf, key);
}
static int
seafile_set_config_int (const char *key, int value, GError **error)
{
return seafile_session_config_set_int(seaf, key, value);
}
static int
seafile_get_config_int (const char *key, GError **error)
{
gboolean exists = TRUE;
int ret = seafile_session_config_get_int(seaf, key, &exists);
if (!exists) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, "Config not exists");
return -1;
}
return ret;
}
static int
seafile_set_upload_rate_limit (int limit, GError **error)
{
if (limit < 0)
limit = 0;
seaf->sync_mgr->upload_limit = limit;
return seafile_session_config_set_int (seaf, KEY_UPLOAD_LIMIT, limit);
}
static int
seafile_set_download_rate_limit (int limit, GError **error)
{
if (limit < 0)
limit = 0;
seaf->sync_mgr->download_limit = limit;
return seafile_session_config_set_int (seaf, KEY_DOWNLOAD_LIMIT, limit);
}
static int
seafile_get_upload_rate(GError **error)
{
return seaf->sync_mgr->last_sent_bytes;
}
static int
seafile_get_download_rate(GError **error)
{
return seaf->sync_mgr->last_recv_bytes;
}
static int
seafile_set_clean_cache_interval (int seconds, GError **error)
{
if (seconds < 0)
return 0;
return file_cache_mgr_set_clean_cache_interval (seaf->file_cache_mgr, seconds);
}
static int
seafile_get_clean_cache_interval (GError **error)
{
return file_cache_mgr_get_clean_cache_interval (seaf->file_cache_mgr);
}
static int
seafile_set_cache_size_limit (gint64 limit, GError **error)
{
if (limit < 0)
return 0;
return file_cache_mgr_set_cache_size_limit (seaf->file_cache_mgr, limit);
}
static gint64
seafile_get_cache_size_limit (GError **error)
{
return file_cache_mgr_get_cache_size_limit (seaf->file_cache_mgr);
}
static int
seafile_is_file_cached (const char *repo_id, const char *file_path, GError **error)
{
return file_cache_mgr_is_file_cached (seaf->file_cache_mgr,
repo_id,
file_path);
}
static json_t *
seafile_get_global_sync_status (GError **error)
{
json_t *object = json_object();
int is_syncing = seaf_sync_manager_is_syncing (seaf->sync_mgr);
int auto_sync_enabled = seaf_sync_manager_is_auto_sync_enabled (seaf->sync_mgr);
json_object_set (object, "auto_sync_enabled", json_integer(auto_sync_enabled));
json_object_set (object, "is_syncing", json_integer(is_syncing));
json_object_set (object, "sent_bytes", json_integer(seaf->sync_mgr->last_sent_bytes));
json_object_set (object, "recv_bytes", json_integer(seaf->sync_mgr->last_recv_bytes));
return object;
}
int
seafile_disable_auto_sync (GError **error)
{
return seaf_sync_manager_disable_auto_sync (seaf->sync_mgr);
}
int
seafile_enable_auto_sync (GError **error)
{
return seaf_sync_manager_enable_auto_sync (seaf->sync_mgr);
}
static char *
seafile_get_path_sync_status (const char *server,
const char *username,
const char *repo_uname,
const char *path,
GError **error)
{
char *repo_id;
char *canon_path = NULL;
char *status;
if (!repo_uname || !path) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Argument should not be null");
return NULL;
}
repo_id = seaf_repo_manager_get_repo_id_by_display_name (seaf->repo_mgr, server, username, repo_uname);
if (!repo_id) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Invalid repo unique name");
return NULL;
}
/* Empty path means to get status of the worktree folder. */
if (strcmp (path, "") != 0) {
canon_path = format_path (path);
} else {
canon_path = g_strdup(path);
}
status = seaf_sync_manager_get_path_sync_status (seaf->sync_mgr,
repo_id,
canon_path);
g_free (repo_id);
g_free (canon_path);
return status;
}
static json_t *
seafile_get_sync_notification (GError **error)
{
return mq_mgr_pop_msg (seaf->mq_mgr, SEADRIVE_NOTIFY_CHAN);
}
static json_t *
seafile_get_events_notification (GError **error)
{
return mq_mgr_pop_msg (seaf->mq_mgr, SEADRIVE_EVENT_CHAN);
}
static char*
seafile_get_repo_id_by_uname (const char *server, const char *username, const char *repo_uname, GError **error)
{
char *repo_id;
if (!repo_uname) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Argument should not be null");
return NULL;
}
repo_id = seaf_repo_manager_get_repo_id_by_display_name (seaf->repo_mgr, server, username, repo_uname);
if (!repo_id) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Invalid repo unique name");
return NULL;
}
return repo_id;
}
static char*
seafile_get_repo_display_name_by_id (const char *repo_id, GError **error)
{
char *repo_uname;
if (!repo_id) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Argument should not be null");
return NULL;
}
repo_uname = seaf_repo_manager_get_repo_display_name (seaf->repo_mgr, repo_id);
if (!repo_uname) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Invalid repo id");
return NULL;
}
return repo_uname;
}
static int
seafile_unmount (GError **error)
{
return seafile_session_unmount (seaf);
}
static json_t *
seafile_get_upload_progress (GError **error)
{
return http_tx_manager_get_upload_progress (seaf->http_tx_mgr);
}
static json_t *
seafile_get_download_progress (GError **error)
{
return file_cache_mgr_get_download_progress (seaf->file_cache_mgr);
}
static int
seafile_mark_file_locked (const char *repo_id, const char *path, GError **error)
{
char *canon_path = NULL;
int len;
int ret;
if (!repo_id || !path) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Argument should not be null");
return -1;
}
if (*path == '/')
++path;
if (path[0] == 0) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Invalid path");
return -1;
}
canon_path = g_strdup(path);
len = strlen(canon_path);
if (canon_path[len-1] == '/')
canon_path[len-1] = 0;
ret = seaf_filelock_manager_mark_file_locked (seaf->filelock_mgr,
repo_id, canon_path, LOCKED_MANUAL);
g_free (canon_path);
return ret;
}
static int
seafile_mark_file_unlocked (const char *repo_id, const char *path, GError **error)
{
char *canon_path = NULL;
int len;
int ret;
if (!repo_id || !path) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Argument should not be null");
return -1;
}
if (*path == '/')
++path;
if (path[0] == 0) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Invalid path");
return -1;
}
canon_path = g_strdup(path);
len = strlen(canon_path);
if (canon_path[len-1] == '/')
canon_path[len-1] = 0;
ret = seaf_filelock_manager_mark_file_unlocked (seaf->filelock_mgr,
repo_id, path);
g_free (canon_path);
return ret;
}
static int
seafile_cancel_download (const char *server, const char *user, const char *full_file_path, GError **error)
{
if (!full_file_path) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Argument should not be null");
return -1;
}
return file_cache_mgr_cancel_download (seaf->file_cache_mgr, server, user, full_file_path, error);
}
static json_t *
seafile_list_sync_errors (GError **error)
{
return seaf_sync_manager_list_sync_errors (seaf->sync_mgr);
}
static int
seafile_cache_path (const char *repo_id, const char *path, GError **error)
{
char *canon_path = NULL;
int len;
if (!repo_id || !path) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Argument should not be null");
return -1;
}
if (*path == '/')
++path;
canon_path = g_strdup(path);
len = strlen(canon_path);
if (len > 0) {
if (canon_path[len-1] == '/')
canon_path[len-1] = 0;
}
seaf_sync_manager_cache_path (seaf->sync_mgr, repo_id, canon_path);
g_free (canon_path);
return 0;
}
int seafile_uncache_path (const char *repo_id, const char *path, GError **error)
{
return file_cache_mgr_uncache_path(repo_id, path);
}
static json_t *
seafile_get_enc_repo_list (GError **error)
{
GList *repos = seaf_repo_manager_get_enc_repo_list (seaf->repo_mgr, -1, -1);
json_t *array, *obj;
array = json_array ();
GList *ptr;
for (ptr = repos; ptr != NULL; ptr = ptr->next) {
SeafRepo *repo = ptr->data;
char *display_name = NULL;
obj = json_object ();
display_name = seaf_repo_manager_get_repo_display_name (seaf->repo_mgr,repo->id);
//used to determine whether the repo is in the current account when switch account.
if (!display_name)
continue;
json_object_set_new (obj, "server", json_string(repo->server));
json_object_set_new (obj, "username", json_string(repo->user));
json_object_set_new (obj, "repo_id", json_string(repo->id));
json_object_set_new (obj, "repo_display_name", json_string(display_name));
if (repo->is_passwd_set)
json_object_set_new (obj, "is_passwd_set", json_true());
else
json_object_set_new (obj, "is_passwd_set", json_false());
json_array_append_new (array, obj);
seaf_repo_unref ((SeafRepo *)ptr->data);
}
g_list_free (repos);
return array;
}
static int
seafile_set_enc_repo_passwd (const char *repo_id, const char *passwd, GError **error)
{
SeafRepo *repo;
int ret = 0;
if (!repo_id || !passwd) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Argument should not be null");
return -1;
}
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "No such library");
return -1;
}
if (repo->pwd_hash_algo) {
if (seafile_pwd_hash_verify_repo_passwd (repo->enc_version, repo_id, passwd,
repo->salt, repo->pwd_hash,
repo->pwd_hash_algo, repo->pwd_hash_params) < 0) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Wrong password");
ret = -1;
goto out;
}
} else {
if (seafile_verify_repo_passwd (repo_id, passwd, repo->magic,
repo->enc_version, repo->salt) < 0) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Wrong password");
ret = -1;
goto out;
}
}
ret = seaf_repo_manager_set_repo_passwd (seaf->repo_mgr, repo, passwd);
if (ret < 0) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Failed to insert data into db");
goto out;
}
out:
seaf_repo_unref (repo);
return ret;
}
static int
seafile_clear_enc_repo_passwd (const char *repo_id, GError **error)
{
int ret = seaf_repo_manager_clear_enc_repo_passwd (repo_id);
if (ret < 0) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Failed to delete data from db");
}
return ret;
}
static int
seafile_add_del_confirmation (const char *confirmation_id, int resync, GError **error)
{
return seaf_sync_manager_add_del_confirmation (seaf->sync_mgr, confirmation_id, resync);
}
static json_t *
seafile_get_account_by_repo_id (const char *repo_id, GError **error)
{
return seaf_repo_manager_get_account_by_repo_id (seaf->repo_mgr, repo_id);
}
static char *
seafile_ping (GError **error)
{
return g_strdup ("pong");
}
#if 0
static GObject *
seafile_generate_magic_and_random_key(int enc_version,
const char* repo_id,
const char *passwd,
GError **error)
{
if (!repo_id || !passwd) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Argument should not be null");
return NULL;
}
gchar magic[65] = {0};
gchar random_key[97] = {0};
seafile_generate_magic (CURRENT_ENC_VERSION, repo_id, passwd, magic);
seafile_generate_random_key (passwd, random_key);
SeafileEncryptionInfo *sinfo;
sinfo = g_object_new (SEAFILE_TYPE_ENCRYPTION_INFO,
"repo_id", repo_id,
"passwd", passwd,
"enc_version", CURRENT_ENC_VERSION,
"magic", magic,
"random_key", random_key,
NULL);
return (GObject *)sinfo;
}
#include "diff-simple.h"
inline static const char*
get_diff_status_str(char status)
{
if (status == DIFF_STATUS_ADDED)
return "add";
if (status == DIFF_STATUS_DELETED)
return "del";
if (status == DIFF_STATUS_MODIFIED)
return "mod";
if (status == DIFF_STATUS_RENAMED)
return "mov";
if (status == DIFF_STATUS_DIR_ADDED)
return "newdir";
if (status == DIFF_STATUS_DIR_DELETED)
return "deldir";
return NULL;
}
static GList *
seafile_diff (const char *repo_id, const char *arg1, const char *arg2, int fold_dir_diff, GError **error)
{
SeafRepo *repo;
char *err_msgs = NULL;
GList *diff_entries, *p;
GList *ret = NULL;
if (!repo_id || !arg1 || !arg2) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Argument should not be null");
return NULL;
}
if (!is_uuid_valid (repo_id)) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Invalid repo id");
return NULL;
}
if ((arg1[0] != 0 && !is_object_id_valid (arg1)) || !is_object_id_valid(arg2)) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Invalid commit id");
return NULL;
}
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "No such repository");
return NULL;
}
diff_entries = seaf_repo_diff (repo, arg1, arg2, fold_dir_diff, &err_msgs);
if (err_msgs) {
g_set_error (error, SEAFILE_DOMAIN, -1, "%s", err_msgs);
g_free (err_msgs);
seaf_repo_unref (repo);
return NULL;
}
seaf_repo_unref (repo);
for (p = diff_entries; p != NULL; p = p->next) {
DiffEntry *de = p->data;
SeafileDiffEntry *entry = g_object_new (
SEAFILE_TYPE_DIFF_ENTRY,
"status", get_diff_status_str(de->status),
"name", de->name,
"new_name", de->new_name,
NULL);
ret = g_list_prepend (ret, entry);
}
for (p = diff_entries; p != NULL; p = p->next) {
DiffEntry *de = p->data;
diff_entry_free (de);
}
g_list_free (diff_entries);
return g_list_reverse (ret);
}
#endif
static void
register_rpc_service ()
{
searpc_server_init (register_marshals);
searpc_create_service ("seadrive-rpcserver");
searpc_server_register_function ("seadrive-rpcserver",
seafile_add_account,
"seafile_add_account",
searpc_signature_int__string_string_string_string_string_int());
searpc_server_register_function ("seadrive-rpcserver",
seafile_delete_account,
"seafile_delete_account",
searpc_signature_int__string_string_int());
searpc_server_register_function ("seadrive-rpcserver",
seafile_get_config,
"seafile_get_config",
searpc_signature_string__string());
searpc_server_register_function ("seadrive-rpcserver",
seafile_set_config,
"seafile_set_config",
searpc_signature_int__string_string());
searpc_server_register_function ("seadrive-rpcserver",
seafile_get_config_int,
"seafile_get_config_int",
searpc_signature_int__string());
searpc_server_register_function ("seadrive-rpcserver",
seafile_set_config_int,
"seafile_set_config_int",
searpc_signature_int__string_int());
searpc_server_register_function ("seadrive-rpcserver",
seafile_set_upload_rate_limit,
"seafile_set_upload_rate_limit",
searpc_signature_int__int());
searpc_server_register_function ("seadrive-rpcserver",
seafile_set_download_rate_limit,
"seafile_set_download_rate_limit",
searpc_signature_int__int());
searpc_server_register_function ("seadrive-rpcserver",
seafile_get_upload_rate,
"seafile_get_upload_rate",
searpc_signature_int__void());
searpc_server_register_function ("seadrive-rpcserver",
seafile_get_download_rate,
"seafile_get_download_rate",
searpc_signature_int__void());
searpc_server_register_function ("seadrive-rpcserver",
seafile_get_clean_cache_interval,
"seafile_get_clean_cache_interval",
searpc_signature_int__void());
searpc_server_register_function ("seadrive-rpcserver",
seafile_set_clean_cache_interval,
"seafile_set_clean_cache_interval",
searpc_signature_int__int());
searpc_server_register_function ("seadrive-rpcserver",
seafile_get_cache_size_limit,
"seafile_get_cache_size_limit",
searpc_signature_int64__void());
searpc_server_register_function ("seadrive-rpcserver",
seafile_is_file_cached,
"seafile_is_file_cached",
searpc_signature_int__string_string());
searpc_server_register_function ("seadrive-rpcserver",
seafile_set_cache_size_limit,
"seafile_set_cache_size_limit",
searpc_signature_int__int64());
searpc_server_register_function ("seadrive-rpcserver",
seafile_get_global_sync_status,
"seafile_get_global_sync_status",
searpc_signature_json__void());
searpc_server_register_function ("seadrive-rpcserver",
seafile_disable_auto_sync,
"seafile_disable_auto_sync",
searpc_signature_int__void());
searpc_server_register_function ("seadrive-rpcserver",
seafile_enable_auto_sync,
"seafile_enable_auto_sync",
searpc_signature_int__void());
searpc_server_register_function ("seadrive-rpcserver",
seafile_get_path_sync_status,
"seafile_get_path_sync_status",
searpc_signature_string__string_string_string_string());
searpc_server_register_function ("seadrive-rpcserver",
seafile_get_sync_notification,
"seafile_get_sync_notification",
searpc_signature_json__void());
searpc_server_register_function ("seadrive-rpcserver",
seafile_get_events_notification,
"seafile_get_events_notification",
searpc_signature_json__void());
searpc_server_register_function ("seadrive-rpcserver",
seafile_get_repo_id_by_uname,
"seafile_get_repo_id_by_uname",
searpc_signature_string__string_string_string());
searpc_server_register_function ("seadrive-rpcserver",
seafile_get_repo_display_name_by_id,
"seafile_get_repo_display_name_by_id",
searpc_signature_string__string());
searpc_server_register_function ("seadrive-rpcserver",
seafile_unmount,
"seafile_unmount",
searpc_signature_int__void());
searpc_server_register_function ("seadrive-rpcserver",
seafile_get_upload_progress,
"seafile_get_upload_progress",
searpc_signature_json__void());
searpc_server_register_function ("seadrive-rpcserver",
seafile_get_download_progress,
"seafile_get_download_progress",
searpc_signature_json__void());
searpc_server_register_function ("seadrive-rpcserver",
seafile_mark_file_locked,
"seafile_mark_file_locked",
searpc_signature_int__string_string());
searpc_server_register_function ("seadrive-rpcserver",
seafile_mark_file_unlocked,
"seafile_mark_file_unlocked",
searpc_signature_int__string_string());
searpc_server_register_function ("seadrive-rpcserver",
seafile_cancel_download,
"seafile_cancel_download",
searpc_signature_int__string_string_string());
searpc_server_register_function ("seadrive-rpcserver",
seafile_list_sync_errors,
"seafile_list_sync_errors",
searpc_signature_json__void());
searpc_server_register_function ("seadrive-rpcserver",
seafile_cache_path,
"seafile_cache_path",
searpc_signature_int__string_string());
searpc_server_register_function ("seadrive-rpcserver",
seafile_uncache_path,
"seafile_uncache_path",
searpc_signature_int__string_string());
searpc_server_register_function ("seadrive-rpcserver",
seafile_get_enc_repo_list,
"seafile_get_enc_repo_list",
searpc_signature_json__void());
searpc_server_register_function ("seadrive-rpcserver",
seafile_set_enc_repo_passwd,
"seafile_set_enc_repo_passwd",
searpc_signature_int__string_string());
searpc_server_register_function ("seadrive-rpcserver",
seafile_clear_enc_repo_passwd,
"seafile_clear_enc_repo_passwd",
searpc_signature_int__string());
searpc_server_register_function ("seadrive-rpcserver",
seafile_add_del_confirmation,
"seafile_add_del_confirmation",
searpc_signature_int__string_int());
searpc_server_register_function ("seadrive-rpcserver",
seafile_get_account_by_repo_id,
"seafile_get_account_by_repo_id",
searpc_signature_json__string());
searpc_server_register_function ("seadrive-rpcserver",
seafile_ping,
"seafile_ping",
searpc_signature_string__void());
#if 0
searpc_server_register_function ("seadrive-rpcserver",
seafile_generate_magic_and_random_key,
"seafile_generate_magic_and_random_key",
searpc_signature_object__int_string_string());
searpc_server_register_function ("seadrive-rpcserver",
seafile_diff,
"seafile_diff",
searpc_signature_objlist__string_string_string_int());
#endif
}
#define SEADRIVE_SOCKET_NAME "seadrive.sock"
int
start_searpc_server ()
{
register_rpc_service ();
char *path = g_build_filename (seaf->seaf_dir, SEADRIVE_SOCKET_NAME, NULL);
SearpcNamedPipeServer *server = searpc_create_named_pipe_server (path);
if (!server) {
seaf_warning ("Failed to create named pipe server.\n");
g_free (path);
return -1;
}
seaf->rpc_socket_path = path;
return searpc_named_pipe_server_start (server);
}
seadrive-fuse-3.0.13/src/rpc-service.h 0000664 0000000 0000000 00000000131 14761776747 0017547 0 ustar 00root root 0000000 0000000 #ifndef SEAF_RPC_SERVER_H
#define SEAF_RPC_SERVER_H
int
start_searpc_server ();
#endif
seadrive-fuse-3.0.13/src/rpc_table.py 0000664 0000000 0000000 00000011250 14761776747 0017465 0 ustar 00root root 0000000 0000000 """
Define RPC functions needed to generate
"""
# [ , [] ]
func_table = [
[ "int", [] ],
[ "int", ["int"] ],
[ "int", ["int", "int"] ],
[ "int", ["int", "string"] ],
[ "int", ["int", "string", "int"] ],
[ "int", ["int", "string", "string"] ],
[ "int", ["int", "string", "int", "int"] ],
[ "int", ["int", "int", "string", "string"] ],
[ "int", ["string"] ],
[ "int", ["string", "int"] ],
[ "int", ["string", "int", "int"] ],
[ "int", ["string", "int", "string"] ],
[ "int", ["string", "int", "string", "string"] ],
[ "int", ["string", "int", "int", "string", "string"] ],
[ "int", ["string", "string"] ],
[ "int", ["string", "string", "string"] ],
[ "int", ["string", "string", "int"] ],
[ "int", ["string", "string", "string", "int"] ],
[ "int", ["string", "string", "string", "string", "int"] ],
[ "int", ["string", "string", "string", "string", "string", "int"] ],
[ "int", ["string", "string", "int", "int"] ],
[ "int", ["string", "string", "string", "string"] ],
[ "int", ["string", "string", "string", "string", "string"] ],
[ "int", ["string", "string", "string", "string", "string", "string"] ],
[ "int", ["string", "string", "string", "string", "string", "string", "string"] ],
[ "int", ["string", "int64"]],
[ "int", ["int", "int64"]],
[ "int", ["int", "string", "int64"]],
[ "int", ["int64"] ],
[ "int64", [] ],
[ "int64", ["string"] ],
[ "int64", ["int"]],
[ "int64", ["int", "string"]],
[ "int64", ["string", "int", "string"] ],
[ "string", [] ],
[ "string", ["int"] ],
[ "string", ["int", "int"] ],
[ "string", ["int", "string"] ],
[ "string", ["int", "int", "string"] ],
[ "string", ["string"] ],
[ "string", ["string", "int"] ],
[ "string", ["string", "int", "int"] ],
[ "string", ["string", "string"] ],
[ "string", ["string", "string", "int"] ],
[ "string", ["string", "string", "int", "int"] ],
[ "string", ["string", "string", "string"] ],
[ "string", ["string", "string", "string", "string"] ],
[ "string", ["string", "string", "string", "string", "int"] ],
[ "string", ["string", "string", "string", "string", "string"] ],
[ "string", ["string", "string", "string", "string", "string", "int"] ],
[ "string", ["string", "string", "string", "string", "string", "string", "int"] ],
[ "string", ["string", "string", "string", "string", "string", "string", "int", "int"] ],
[ "string", ["string", "string", "string", "string", "string", "string"] ],
[ "string", ["string", "string", "string", "string", "string", "string", "int64"] ],
[ "string", ["string", "string", "string", "string", "string", "string", "int64", "int"] ],
[ "string", ["string", "string", "string", "string", "string", "string", "string"] ],
[ "string", ["string", "string", "string", "string", "string", "string", "string", "int64"] ],
[ "string", ["string", "string", "string", "string", "string", "string", "string", "string", "string"] ],
[ "string", ["string", "int", "string", "string", "string", "string", "string", "string", "string", "string", "string", "string", "int", "string"] ],
[ "string", ["string", "int", "string", "int", "int"] ],
[ "string", ["string", "int", "string", "string", "string"] ],
[ "objlist", [] ],
[ "objlist", ["int"] ],
[ "objlist", ["int", "int"] ],
[ "objlist", ["int", "string"] ],
[ "objlist", ["int", "int", "int"] ],
[ "objlist", ["string"] ],
[ "objlist", ["string", "int"] ],
[ "objlist", ["string", "int", "int"] ],
[ "objlist", ["string", "int", "string"] ],
[ "objlist", ["string", "string"] ],
[ "objlist", ["string", "string", "string"] ],
[ "objlist", ["string", "string", "int"] ],
[ "objlist", ["string", "string", "string", "int"] ],
[ "objlist", ["string", "string", "int", "int"] ],
[ "objlist", ["string", "string", "int", "int", "int"] ],
[ "objlist", ["int", "string", "string", "int", "int"] ],
[ "objlist", ["string", "int", "string", "string", "string"] ],
[ "objlist", ["string", "int", "string", "int", "int"] ],
[ "objlist", ["string", "int", "string", "string", "int"] ],
[ "objlist", ["string", "string", "string", "string", "int", "int"] ],
[ "object", [] ],
[ "object", ["int"] ],
[ "object", ["string"] ],
[ "object", ["string", "string"] ],
[ "object", ["string", "string", "string"] ],
[ "object", ["string", "int", "string"] ],
[ "object", ["int", "string", "string"] ],
[ "object", ["string", "string", "string", "string", "string", "string", "string", "int", "int"] ],
[ "json", []],
[ "json", ["string"]],
]
seadrive-fuse-3.0.13/src/seadrive.c 0000664 0000000 0000000 00000036204 14761776747 0017134 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#include "common.h"
#include
#include
#include
#include
#include
#include
#include
#ifdef HAVE_BREAKPAD_SUPPORT
#include
#endif // HAVE_BREAKPAD_SUPPORT
#include "seafile-session.h"
#include "log.h"
#include "utils.h"
#include "seafile-config.h"
#ifndef USE_GPL_CRYPTO
#include "curl-init.h"
#endif
#include "cdc.h"
#include "rpc-service.h"
#ifndef SEAFILE_CLIENT_VERSION
#define SEAFILE_CLIENT_VERSION PACKAGE_VERSION
#endif
SeafileSession *seaf;
static void print_version ()
{
fprintf (stdout, "SeaDrive "SEAFILE_CLIENT_VERSION"\n");
}
#include
#include
#define FUSE_USE_VERSION 26
#include
#include
#include "fuse-ops.h"
struct options {
char *seafile_dir;
char *log_file;
char *debug_str;
char *config_file;
char *language;
} options;
#define SEAF_FUSE_OPT_KEY(t, p, v) { t, offsetof(struct options, p), v }
enum {
KEY_VERSION,
KEY_HELP,
};
static struct fuse_opt seadrive_fuse_opts[] = {
SEAF_FUSE_OPT_KEY("-d %s", seafile_dir, 0),
SEAF_FUSE_OPT_KEY("--seafdir %s", seafile_dir, 0),
SEAF_FUSE_OPT_KEY("-l %s", log_file, 0),
SEAF_FUSE_OPT_KEY("--logfile %s", log_file, 0),
SEAF_FUSE_OPT_KEY("-D %s", debug_str, 0),
SEAF_FUSE_OPT_KEY("--debug %s", debug_str, 0),
SEAF_FUSE_OPT_KEY("-c %s", config_file, 0),
SEAF_FUSE_OPT_KEY("--config %s", config_file, 0),
SEAF_FUSE_OPT_KEY("-L %s", language, 0),
SEAF_FUSE_OPT_KEY("--language %s", language, 0),
FUSE_OPT_KEY("-V", KEY_VERSION),
FUSE_OPT_KEY("--version", KEY_VERSION),
FUSE_OPT_KEY("-h", KEY_HELP),
FUSE_OPT_KEY("--help", KEY_HELP),
FUSE_OPT_END
};
static struct fuse_operations seadrive_fuse_ops = {
.getattr = seadrive_fuse_getattr,
.readdir = seadrive_fuse_readdir,
.mknod = seadrive_fuse_mknod,
.mkdir = seadrive_fuse_mkdir,
.unlink = seadrive_fuse_unlink,
.rmdir = seadrive_fuse_rmdir,
.rename = seadrive_fuse_rename,
.open = seadrive_fuse_open,
.read = seadrive_fuse_read,
.write = seadrive_fuse_write,
.release = seadrive_fuse_release,
.truncate = seadrive_fuse_truncate,
.statfs = seadrive_fuse_statfs,
.chmod = seadrive_fuse_chmod,
.utimens = seadrive_fuse_utimens,
.symlink = seadrive_fuse_symlink,
/* .setxattr = seadrive_fuse_setxattr, */
/* .getxattr = seadrive_fuse_getxattr, */
/* .listxattr = seadrive_fuse_listxattr, */
/* .removexattr = seadrive_fuse_removexattr */
};
static void usage ()
{
fprintf (stderr, "usage: seadrive -d \n"
"Options:\n"
"-f: run in foreground\n"
"-o : please refer to fuse manpage\n"
"-l \n"
"-c \n"
"-L \n");
}
static int
seadrive_fuse_opt_proc_func(void *data, const char *arg, int key,
struct fuse_args *outargs)
{
if (key == KEY_VERSION) {
print_version ();
exit (0);
} else if (key == KEY_HELP) {
usage ();
exit (0);
}
return 1;
}
static void
set_signal_handlers (SeafileSession *session)
{
signal (SIGPIPE, SIG_IGN);
}
static void *
seafile_session_thread (void *vdata)
{
seafile_session_start (seaf);
return NULL;
}
#define ACCOUNT_GROUP "account"
#define ACCOUNT_SERVER "server"
#define ACCOUNT_USERNAME "username"
#define ACCOUNT_TOKEN "token"
#define ACCOUNT_IS_PRO "is_pro"
static void
load_account_from_file (const char *account_file)
{
GKeyFile *key_file = g_key_file_new ();
char *full_account_file = NULL;
GError *error = NULL;
char *server = NULL, *username = NULL, *token = NULL;
gboolean is_pro;
full_account_file = ccnet_expand_path (account_file);
if (!g_key_file_load_from_file (key_file, full_account_file, 0, &error)) {
seaf_warning ("Failed to load account file %s: %s.\n",
full_account_file, error->message);
g_clear_error (&error);
goto out;
}
server = g_key_file_get_string (key_file, ACCOUNT_GROUP, ACCOUNT_SERVER, &error);
if (!server) {
g_clear_error (&error);
goto out;
}
username = g_key_file_get_string (key_file, ACCOUNT_GROUP, ACCOUNT_USERNAME, &error);
if (!username) {
g_clear_error (&error);
goto out;
}
token = g_key_file_get_string (key_file, ACCOUNT_GROUP, ACCOUNT_TOKEN, &error);
if (!token) {
g_clear_error (&error);
goto out;
}
/* Default false. */
is_pro = g_key_file_get_boolean (key_file, ACCOUNT_GROUP, ACCOUNT_IS_PRO, NULL);
seaf_repo_manager_add_account (seaf->repo_mgr,
server, username,
username,
token,
username,
is_pro);
out:
g_free (server);
g_free (username);
g_free (token);
g_free (full_account_file);
g_key_file_free (key_file);
}
#define GENERAL_GROUP "general"
#define CLIENT_NAME "client_name"
#define DELETE_THRESHOLD "delete_confirm_threshold"
#define NETWORK_GROUP "network"
#define DISABLE_VERIFY_CERTIFICATE "disable_verify_certificate"
#define PROXY_GROUP "proxy"
#define PROXY_TYPE "type"
#define PROXY_ADDR "addr"
#define PROXY_PORT "port"
#define PROXY_USERNAME "username"
#define PROXY_PASSWORD "password"
#define CACHE_GROUP "cache"
#define CACHE_SIZE_LIMIT "size_limit"
#define CLEAN_CACHE_INTERVAL "clean_cache_interval"
static gint64
convert_size_str (const char *size_str)
{
#define KB 1000L
#define MB 1000000L
#define GB 1000000000L
#define TB 1000000000000L
char *end;
gint64 size_int;
gint64 multiplier = GB;
gint64 size;
size_int = strtoll (size_str, &end, 10);
if (size_int == LLONG_MIN || size_int == LLONG_MAX) {
return -1;
}
if (*end != '\0') {
if (strcasecmp(end, "kb") == 0 || strcasecmp(end, "k") == 0)
multiplier = KB;
else if (strcasecmp(end, "mb") == 0 || strcasecmp(end, "m") == 0)
multiplier = MB;
else if (strcasecmp(end, "gb") == 0 || strcasecmp(end, "g") == 0)
multiplier = GB;
else if (strcasecmp(end, "tb") == 0 || strcasecmp(end, "t") == 0)
multiplier = TB;
else {
seaf_warning ("Unrecognized %s\n", size_str);
return -1;
}
}
size = size_int * multiplier;
return size;
}
static void
load_config_from_file (const char *config_file)
{
GKeyFile *key_file = g_key_file_new ();
char *full_config_file = NULL;
GError *error = NULL;
char *client_name = NULL;
gboolean disable_verify_certificate;
char *proxy_type = NULL, *proxy_addr = NULL, *proxy_username = NULL, *proxy_password = NULL;
int proxy_port;
char *cache_size_limit_str = NULL;
gint64 cache_size_limit;
int clean_cache_interval;
int delete_confirm_threshold = 1000000;
full_config_file = ccnet_expand_path (config_file);
if (!g_key_file_load_from_file (key_file, full_config_file, 0, &error)) {
seaf_warning ("Failed to load config file %s: %s.\n",
full_config_file, error->message);
g_clear_error (&error);
g_free (full_config_file);
return;
}
client_name = g_key_file_get_string (key_file, GENERAL_GROUP, CLIENT_NAME, NULL);
if (client_name) {
g_free (seaf->client_name);
seaf->client_name = g_strdup(client_name);
}
delete_confirm_threshold = g_key_file_get_integer (key_file, GENERAL_GROUP, DELETE_THRESHOLD,
&error);
if (!error) {
if (delete_confirm_threshold > 0)
seaf->delete_confirm_threshold = delete_confirm_threshold;
}
g_clear_error (&error);
disable_verify_certificate = g_key_file_get_boolean (key_file, NETWORK_GROUP,
DISABLE_VERIFY_CERTIFICATE,
&error);
if (!error) {
seaf->disable_verify_certificate = disable_verify_certificate;
}
g_clear_error (&error);
proxy_type = g_key_file_get_string (key_file, PROXY_GROUP, PROXY_TYPE, &error);
if (!error &&
(g_strcmp0 (proxy_type, "http") == 0 ||
g_strcmp0 (proxy_type, "socks") == 0)) {
seaf->use_http_proxy = TRUE;
g_free (seaf->http_proxy_type);
seaf->http_proxy_type = g_strdup(proxy_type);
proxy_addr = g_key_file_get_string (key_file, PROXY_GROUP, PROXY_ADDR, &error);
if (!error) {
g_free (seaf->http_proxy_addr);
seaf->http_proxy_addr = g_strdup(proxy_addr);
}
g_clear_error (&error);
proxy_port = g_key_file_get_integer (key_file, PROXY_GROUP, PROXY_PORT, &error);
if (!error) {
seaf->http_proxy_port = proxy_port;
}
g_clear_error (&error);
proxy_username = g_key_file_get_string (key_file, PROXY_GROUP, PROXY_USERNAME, &error);
if (!error) {
g_free (seaf->http_proxy_username);
seaf->http_proxy_username = g_strdup(proxy_username);
}
g_clear_error (&error);
proxy_password = g_key_file_get_string (key_file, PROXY_GROUP, PROXY_PASSWORD, &error);
if (!error) {
g_free (seaf->http_proxy_password);
seaf->http_proxy_password = g_strdup(proxy_password);
}
g_clear_error (&error);
}
g_clear_error (&error);
cache_size_limit_str = g_key_file_get_string (key_file, CACHE_GROUP, CACHE_SIZE_LIMIT,
&error);
if (!error) {
cache_size_limit = convert_size_str (cache_size_limit_str);
if (cache_size_limit >= 0)
file_cache_mgr_set_cache_size_limit (seaf->file_cache_mgr, cache_size_limit);
}
g_clear_error (&error);
clean_cache_interval = g_key_file_get_integer (key_file, CACHE_GROUP, CLEAN_CACHE_INTERVAL,
&error);
if (!error) {
file_cache_mgr_set_clean_cache_interval (seaf->file_cache_mgr, clean_cache_interval * 60);
}
g_clear_error (&error);
g_free (client_name);
g_free (proxy_type);
g_free (proxy_addr);
g_free (proxy_username);
g_free (proxy_password);
g_free (cache_size_limit_str);
g_free (full_config_file);
g_key_file_free (key_file);
}
static int
write_pidfile (char *pidfile)
{
int fd = open (pidfile, O_WRONLY | O_CREAT, 0644);
if (fd < 0) {
seaf_warning ("Failed to open pidfile %s: %s\n",
pidfile, strerror(errno));
return -1;
}
if (flock (fd, LOCK_EX | LOCK_NB) < 0) {
seaf_warning ("Failed to lock pidfile %s: %s\n",
pidfile, strerror(errno));
close (fd);
return -1;
}
return 0;
}
int
main (int argc, char **argv)
{
char *seafile_dir = NULL;
char *full_seafdir = NULL, *log_dir = NULL;
char *logfile = NULL;
const char *debug_str = NULL;
const char *mount_point = NULL;
char *config_file = NULL;
char *language = NULL;
char *pidfile = NULL;
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
memset(&options, 0, sizeof(struct options));
if (fuse_opt_parse(&args, &options, seadrive_fuse_opts,
seadrive_fuse_opt_proc_func) == -1) {
usage();
exit(1);
}
seafile_dir = options.seafile_dir;
logfile = options.log_file;
debug_str = options.debug_str;
config_file = options.config_file;
language = options.language;
mount_point = args.argc[args.argv - 1];
if (!seafile_dir) {
usage ();
exit(1);
}
if (!mount_point) {
usage ();
exit (1);
}
if (language != NULL &&
strcmp (language, "zh_cn") != 0 &&
strcmp (language, "en_us") != 0 &&
strcmp (language, "fr_fr") != 0 &&
strcmp (language, "de_de") != 0) {
fprintf (stderr, "Unknown language %s\n", language);
exit (1);
}
cdc_init ();
#if !GLIB_CHECK_VERSION(2, 35, 0)
g_type_init();
#endif
#if !GLIB_CHECK_VERSION(2, 31, 0)
g_thread_init(NULL);
#endif
full_seafdir = ccnet_expand_path (seafile_dir);
fprintf (stderr, "seafile dir: %s\n", full_seafdir);
log_dir = g_build_filename (full_seafdir, "logs", NULL);
if (checkdir_with_mkdir (log_dir) < 0) {
fprintf (stderr, "Failed to create logs directory.\n");
exit (1);
}
if (!debug_str)
debug_str = g_getenv("SEADRIVE_DEBUG");
seafile_debug_set_flags_string (debug_str);
if (logfile == NULL)
logfile = g_build_filename (log_dir, "seadrive.log", NULL);
if (seafile_log_init (logfile) < 0) {
seaf_warning ("Failed to init log.\n");
exit (1);
}
pidfile = g_build_filename (full_seafdir, "seadrive.pid", NULL);
if (write_pidfile (pidfile) < 0) {
seaf_warning ("Seadrive is already running.\n");
exit (1);
}
#if defined(HAVE_BREAKPAD_SUPPORT)
char *real_logdir = g_path_get_dirname (logfile);
char *dump_dir = g_build_filename (real_logdir, "dumps", NULL);
checkdir_with_mkdir(dump_dir);
CBPWrapperExceptionHandler bp_exception_handler = newCBPWrapperExceptionHandler(dump_dir);
g_free (real_logdir);
g_free (dump_dir);
#endif
g_free (full_seafdir);
g_free (log_dir);
g_free (logfile);
set_signal_handlers (seaf);
#ifndef USE_GPL_CRYPTO
seafile_curl_init();
#endif
seaf = seafile_session_new (seafile_dir);
if (!seaf) {
seaf_warning ("Failed to create seafile session.\n");
exit (1);
}
if (mount_point) {
seaf->mount_point = ccnet_expand_path (mount_point);
}
if (g_strcmp0 (language, "zh_cn") == 0)
seaf->language = SEAF_LANG_ZH_CN;
else if (g_strcmp0 (language, "fr_fr") == 0)
seaf->language = SEAF_LANG_FR_FR;
else if (g_strcmp0 (language, "de_de") == 0)
seaf->language = SEAF_LANG_DE_DE;
else
seaf->language = SEAF_LANG_EN_US;
seaf_message ("Starting SeaDrive client "SEAFILE_CLIENT_VERSION"\n");
#if defined(SEADRIVE_SOURCE_COMMIT_ID)
seaf_message ("SeaDrive client source code version "SEADRIVE_SOURCE_COMMIT_ID"\n");
#endif
seafile_session_prepare (seaf);
if (config_file) {
load_account_from_file (config_file);
load_config_from_file (config_file);
}
/* Start seafile session thread. */
pthread_t tid;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
if (pthread_create (&tid, &attr, seafile_session_thread, NULL) < 0) {
seaf_warning ("Failed to create seafile session thread.\n");
exit (1);
}
if (start_searpc_server () < 0) {
seaf_warning ("Failed to start searpc server.\n");
exit (1);
}
seaf_message ("rpc server started.\n");
fuse_main(args.argc, args.argv, &seadrive_fuse_ops, NULL);
fuse_opt_free_args (&args);
#ifndef USE_GPL_CRYPTO
seafile_curl_deinit();
#endif
return 0;
}
seadrive-fuse-3.0.13/src/seafile-config.c 0000664 0000000 0000000 00000014544 14761776747 0020210 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#include "common.h"
#include "db.h"
#include "seafile-config.h"
gboolean
seafile_session_config_exists (SeafileSession *session, const char *key)
{
char sql[256];
snprintf (sql, sizeof(sql),
"SELECT 1 FROM Config WHERE key = '%s'",
key);
return sqlite_check_for_existence (session->config_db, sql);
}
static gboolean
get_value (sqlite3_stmt *stmt, void *data)
{
char **p_value = data;
*p_value = g_strdup((char *) sqlite3_column_text (stmt, 0));
/* Only one result. */
return FALSE;
}
static char *
config_get_string (sqlite3 *config_db, const char *key)
{
char sql[256];
char *value = NULL;
snprintf (sql, sizeof(sql),
"SELECT value FROM Config WHERE key='%s';",
key);
if (sqlite_foreach_selected_row (config_db, sql,
get_value, &value) < 0)
return NULL;
return value;
}
char *
seafile_session_config_get_string (SeafileSession *session,
const char *key)
{
return (config_get_string (session->config_db, key));
}
int
seafile_session_config_get_int (SeafileSession *session,
const char *key,
gboolean *exists)
{
char *value;
int ret;
value = config_get_string (session->config_db, key);
if (!value) {
if (exists)
*exists = FALSE;
return -1;
}
if (exists)
*exists = TRUE;
ret = atoi (value);
g_free (value);
return ret;
}
gint64
seafile_session_config_get_int64 (SeafileSession *session,
const char *key,
gboolean *exists)
{
char *value;
gint64 ret;
value = config_get_string (session->config_db, key);
if (!value) {
if (exists)
*exists = FALSE;
return -1;
}
if (exists)
*exists = TRUE;
ret = strtoll(value, NULL, 10);
g_free (value);
return ret;
}
gboolean
seafile_session_config_get_bool (SeafileSession *session,
const char *key)
{
char *value;
gboolean ret = FALSE;
value = config_get_string (session->config_db, key);
if (g_strcmp0(value, "true") == 0)
ret = TRUE;
g_free (value);
return ret;
}
int
seafile_session_config_set_string (SeafileSession *session,
const char *key,
const char *value)
{
char sql[256];
sqlite3_snprintf (sizeof(sql), sql,
"REPLACE INTO Config VALUES ('%q', '%q');",
key, value);
if (sqlite_query_exec (session->config_db, sql) < 0)
return -1;
if (g_strcmp0 (key, KEY_CLIENT_ID) == 0) {
g_free (session->client_id);
session->client_id = g_strdup(value);
}
if (g_strcmp0 (key, KEY_CLIENT_NAME) == 0) {
g_free (session->client_name);
session->client_name = g_strdup(value);
}
if (g_strcmp0(key, KEY_SYNC_EXTRA_TEMP_FILE) == 0) {
if (g_strcmp0(value, "true") == 0)
session->sync_extra_temp_file = TRUE;
else
session->sync_extra_temp_file = FALSE;
}
if (g_strcmp0(key, KEY_DISABLE_VERIFY_CERTIFICATE) == 0) {
if (g_strcmp0(value, "true") == 0)
session->disable_verify_certificate = TRUE;
else
session->disable_verify_certificate = FALSE;
}
if (g_strcmp0(key, KEY_USE_PROXY) == 0) {
if (g_strcmp0(value, "true") == 0)
session->use_http_proxy = TRUE;
else
session->use_http_proxy = FALSE;
}
if (g_strcmp0(key, KEY_PROXY_TYPE) == 0) {
session->http_proxy_type =
g_strcmp0(value, "none") == 0 ? NULL : g_strdup(value);
}
if (g_strcmp0(key, KEY_PROXY_ADDR) == 0) {
session->http_proxy_addr = g_strdup(value);
}
if (g_strcmp0(key, KEY_PROXY_USERNAME) == 0) {
session->http_proxy_username = g_strdup(value);
}
if (g_strcmp0(key, KEY_PROXY_PASSWORD) == 0) {
session->http_proxy_password = g_strdup(value);
}
if (g_strcmp0 (key, KEY_ENABLE_AUTO_LOCK) == 0) {
if (g_strcmp0 (value, "true") == 0)
session->enable_auto_lock = TRUE;
else
session->enable_auto_lock = FALSE;
}
if (g_strcmp0(key, KEY_HIDE_WINDOWS_INCOMPATIBLE_PATH_NOTIFICATION) == 0) {
if (g_strcmp0(value, "true") == 0)
session->hide_windows_incompatible_path_notification = TRUE;
else
session->hide_windows_incompatible_path_notification = FALSE;
}
return 0;
}
int
seafile_session_config_set_int (SeafileSession *session,
const char *key,
int value)
{
char sql[256];
sqlite3_snprintf (sizeof(sql), sql,
"REPLACE INTO Config VALUES ('%q', %d);",
key, value);
if (sqlite_query_exec (session->config_db, sql) < 0)
return -1;
if (g_strcmp0(key, KEY_PROXY_PORT) == 0) {
session->http_proxy_port = value;
}
if (g_strcmp0 (key, DELETE_CONFIRM_THRESHOLD) == 0) {
session->delete_confirm_threshold = value;
}
return 0;
}
int
seafile_session_config_set_int64 (SeafileSession *session,
const char *key,
gint64 value)
{
char sql[256];
sqlite3_snprintf (sizeof(sql), sql,
"REPLACE INTO Config VALUES ('%q', %lld);",
key, value);
if (sqlite_query_exec (session->config_db, sql) < 0)
return -1;
return 0;
}
sqlite3 *
seafile_session_config_open_db (const char *db_path)
{
sqlite3 *db;
if (sqlite_open_db (db_path, &db) < 0)
return NULL;
/*
* Values are stored in text. You should convert it
* back to integer if needed when you read it from
* db.
*/
char *sql = "CREATE TABLE IF NOT EXISTS Config ("
"key TEXT PRIMARY KEY, "
"value TEXT);";
sqlite_query_exec (db, sql);
return db;
}
gboolean
seafile_session_config_get_allow_repo_not_found_on_server(SeafileSession *session)
{
return seafile_session_config_get_bool (session,
KEY_ALLOW_REPO_NOT_FOUND_ON_SERVER);
}
seadrive-fuse-3.0.13/src/seafile-config.h 0000664 0000000 0000000 00000005645 14761776747 0020217 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#ifndef SEAFILE_CONFIG_H
#define SEAFILE_CONFIG_H
#include "seafile-session.h"
#include "db.h"
#define KEY_CLIENT_ID "client_id"
#define KEY_CLIENT_NAME "client_name"
#define KEY_UPLOAD_LIMIT "upload_limit"
#define KEY_DOWNLOAD_LIMIT "download_limit"
#define KEY_ALLOW_REPO_NOT_FOUND_ON_SERVER "allow_repo_not_found_on_server"
#define KEY_SYNC_EXTRA_TEMP_FILE "sync_extra_temp_file"
/* Http sync settings. */
#define KEY_ENABLE_HTTP_SYNC "enable_http_sync"
#define KEY_DISABLE_VERIFY_CERTIFICATE "disable_verify_certificate"
/* Http sync proxy settings. */
#define KEY_USE_PROXY "use_proxy"
#define KEY_PROXY_TYPE "proxy_type"
#define KEY_PROXY_ADDR "proxy_addr"
#define KEY_PROXY_PORT "proxy_port"
#define KEY_PROXY_USERNAME "proxy_username"
#define KEY_PROXY_PASSWORD "proxy_password"
#define PROXY_TYPE_HTTP "http"
#define PROXY_TYPE_SOCKS "socks"
/* Cache cleaning settings. */
#define KEY_CACHE_SIZE_LIMIT "cache_size_limit"
#define KEY_CLEAN_CACHE_INTERVAL "clean_cache_interval"
#define KEY_ENABLE_AUTO_LOCK "enable_auto_lock"
#define KEY_CURRENT_SESSION_ACCESS "current_session_access"
#define DELETE_CONFIRM_THRESHOLD "delete_confirm_threshold"
#define KEY_HIDE_WINDOWS_INCOMPATIBLE_PATH_NOTIFICATION "hide_windows_incompatible_path_notification"
gboolean
seafile_session_config_exists (SeafileSession *session, const char *key);
/*
* Returns: config value in string. The string should be freed by caller.
*/
char *
seafile_session_config_get_string (SeafileSession *session,
const char *key);
/*
* Returns:
* If key exists, @exists will be set to TRUE and returns the value;
* otherwise, @exists will be set to FALSE and returns -1.
*/
int
seafile_session_config_get_int (SeafileSession *session,
const char *key,
gboolean *exists);
gint64
seafile_session_config_get_int64 (SeafileSession *session,
const char *key,
gboolean *exists);
/*
* Returns: config value in boolean. Return FALSE if the value is not configured.
*/
gboolean
seafile_session_config_get_bool (SeafileSession *session,
const char *key);
int
seafile_session_config_set_string (SeafileSession *session,
const char *key,
const char *value);
int
seafile_session_config_set_int (SeafileSession *session,
const char *key,
int value);
int
seafile_session_config_set_int64 (SeafileSession *session,
const char *key,
gint64 value);
gboolean
seafile_session_config_get_allow_repo_not_found_on_server(SeafileSession *session);
sqlite3 *
seafile_session_config_open_db (const char *db_path);
#endif
seadrive-fuse-3.0.13/src/seafile-crypt.c 0000664 0000000 0000000 00000051543 14761776747 0020104 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#include "common.h"
#include
#include
#include "utils.h"
#include "seafile-crypt.h"
#ifdef USE_GPL_CRYPTO
#include
#include
#include
#else
#include
#include
#include
#endif
#include "password-hash.h"
#include "log.h"
/*
The EVP_EncryptXXX and EVP_DecryptXXX series of functions have a
weird choice of returned value.
*/
#define ENC_SUCCESS 1
#define ENC_FAILURE 0
#define DEC_SUCCESS 1
#define DEC_FAILURE 0
#define KEYGEN_ITERATION 1 << 19
#define KEYGEN_ITERATION2 1000
/* Should generate random salt for each repo. */
static unsigned char salt[8] = { 0xda, 0x90, 0x45, 0xc3, 0x06, 0xc7, 0xcc, 0x26 };
SeafileCrypt *
seafile_crypt_new (int version, unsigned char *key, unsigned char *iv)
{
SeafileCrypt *crypt = g_new0 (SeafileCrypt, 1);
crypt->version = version;
if (version == 1)
memcpy (crypt->key, key, 16);
else
memcpy (crypt->key, key, 32);
memcpy (crypt->iv, iv, 16);
return crypt;
}
int
seafile_derive_key (const char *data_in, int in_len, int version,
const char *repo_salt,
unsigned char *key, unsigned char *iv)
{
#ifdef USE_GPL_CRYPTO
unsigned char repo_salt_bin[32];
if (version == 4)
hex_to_rawdata (repo_salt, repo_salt_bin, 32);
switch (version) {
case 1:
seaf_warning ("Encrypted library version %d is not supported.\n", version);
return -1;
case 2:
pbkdf2_hmac_sha256 (in_len, (const guchar *)data_in, KEYGEN_ITERATION2,
sizeof(salt), salt, 32, key);
pbkdf2_hmac_sha256 (32, (const guchar *)key, 10, sizeof(salt), salt, 16, iv);
case 3:
seaf_warning ("Encrypted library version %d is not supported.\n", version);
return -1;
case 4:
pbkdf2_hmac_sha256 (in_len, (const guchar *)data_in, KEYGEN_ITERATION2,
sizeof(repo_salt_bin), repo_salt_bin, 32, key);
pbkdf2_hmac_sha256 (32, (const guchar *)key, 10, sizeof(repo_salt_bin), repo_salt_bin, 16, iv);
default:
seaf_warning ("Encrypted library version %d is not supported.\n", version);
return -1;
}
return 0;
#else
if (version >= 3) {
unsigned char repo_salt_bin[32];
hex_to_rawdata (repo_salt, repo_salt_bin, 32);
PKCS5_PBKDF2_HMAC (data_in, in_len,
repo_salt_bin, sizeof(repo_salt_bin),
KEYGEN_ITERATION2,
EVP_sha256(),
32, key);
PKCS5_PBKDF2_HMAC ((char *)key, 32,
repo_salt_bin, sizeof(repo_salt_bin),
10,
EVP_sha256(),
16, iv);
return 0;
} else if (version == 2) {
PKCS5_PBKDF2_HMAC (data_in, in_len,
salt, sizeof(salt),
KEYGEN_ITERATION2,
EVP_sha256(),
32, key);
PKCS5_PBKDF2_HMAC ((char *)key, 32,
salt, sizeof(salt),
10,
EVP_sha256(),
16, iv);
return 0;
} else if (version == 1)
return EVP_BytesToKey (EVP_aes_128_cbc(), /* cipher mode */
EVP_sha1(), /* message digest */
salt, /* salt */
(unsigned char*)data_in,
in_len,
KEYGEN_ITERATION, /* iteration times */
key, /* the derived key */
iv); /* IV, initial vector */
else
return EVP_BytesToKey (EVP_aes_128_ecb(), /* cipher mode */
EVP_sha1(), /* message digest */
NULL, /* salt */
(unsigned char*)data_in,
in_len,
3, /* iteration times */
key, /* the derived key */
iv); /* IV, initial vector */
#endif
}
int
seafile_generate_random_key (const char *passwd, char *random_key,
int version, const char *repo_salt)
{
SeafileCrypt *crypt;
unsigned char secret_key[32], *rand_key;
int outlen;
unsigned char key[32], iv[16];
#ifdef USE_GPL_CRYPTO
if (gnutls_rnd (GNUTLS_RND_RANDOM, secret_key, sizeof(secret_key)) < 0) {
seaf_warning ("Failed to generate secret key for repo encryption.\n");
return -1;
}
#else
if (RAND_bytes (secret_key, sizeof(secret_key)) != 1) {
seaf_warning ("Failed to generate secret key for repo encryption "
"with RAND_bytes(), use RAND_pseudo_bytes().\n");
return -1;
}
#endif
seafile_derive_key (passwd, strlen(passwd), version, repo_salt, key, iv);
crypt = seafile_crypt_new (version, key, iv);
seafile_encrypt ((char **)&rand_key, &outlen,
(char *)secret_key, sizeof(secret_key), crypt);
rawdata_to_hex (rand_key, random_key, 48);
g_free (crypt);
g_free (rand_key);
return 0;
}
void
seafile_generate_magic (int version, const char *repo_id,
const char *passwd,
const char *repo_salt,
char *magic)
{
GString *buf = g_string_new (NULL);
unsigned char key[32], iv[16];
/* Compute a "magic" string from repo_id and passwd.
* This is used to verify the password given by user before decrypting
* data.
*/
g_string_append_printf (buf, "%s%s", repo_id, passwd);
seafile_derive_key (buf->str, buf->len, version, repo_salt, key, iv);
g_string_free (buf, TRUE);
rawdata_to_hex (key, magic, 32);
}
void
seafile_generate_pwd_hash (int version,
const char *repo_id,
const char *passwd,
const char *repo_salt,
const char *algo,
const char *params_str,
char *pwd_hash)
{
GString *buf = g_string_new (NULL);
unsigned char key[32];
/* Compute a "pwd_hash" string from repo_id and passwd.
* This is used to verify the password given by user before decrypting
* data.
*/
g_string_append_printf (buf, "%s%s", repo_id, passwd);
if (version <= 2) {
// use fixed repo salt
char fixed_salt[64] = {0};
rawdata_to_hex(salt, fixed_salt, 8);
pwd_hash_derive_key (buf->str, buf->len, fixed_salt, algo, params_str, key);
} else {
pwd_hash_derive_key (buf->str, buf->len, repo_salt, algo, params_str, key);
}
g_string_free (buf, TRUE);
rawdata_to_hex (key, pwd_hash, 32);
}
int
seafile_verify_repo_passwd (const char *repo_id,
const char *passwd,
const char *magic,
int version,
const char *repo_salt)
{
GString *buf = g_string_new (NULL);
unsigned char key[32], iv[16];
char hex[65];
if (version != 1 && version != 2 && version != 3 && version != 4) {
seaf_warning ("Unsupported enc_version %d.\n", version);
return -1;
}
/* Recompute the magic and compare it with the one comes with the repo. */
g_string_append_printf (buf, "%s%s", repo_id, passwd);
seafile_derive_key (buf->str, buf->len, version, repo_salt, key, iv);
g_string_free (buf, TRUE);
if (version >= 2)
rawdata_to_hex (key, hex, 32);
else
rawdata_to_hex (key, hex, 16);
if (g_strcmp0 (hex, magic) == 0)
return 0;
else
return -1;
}
int
seafile_pwd_hash_verify_repo_passwd (int version,
const char *repo_id,
const char *passwd,
const char *repo_salt,
const char *pwd_hash,
const char *algo,
const char *params_str)
{
GString *buf = g_string_new (NULL);
unsigned char key[32];
char hex[65];
g_string_append_printf (buf, "%s%s", repo_id, passwd);
if (version <= 2) {
// use fixed repo salt
char fixed_salt[64] = {0};
rawdata_to_hex(salt, fixed_salt, 8);
pwd_hash_derive_key (buf->str, buf->len, fixed_salt, algo, params_str, key);
} else {
pwd_hash_derive_key (buf->str, buf->len, repo_salt, algo, params_str, key);
}
g_string_free (buf, TRUE);
rawdata_to_hex (key, hex, 32);
if (g_strcmp0 (hex, pwd_hash) == 0)
return 0;
else
return -1;
}
int
seafile_decrypt_repo_enc_key (int enc_version,
const char *passwd, const char *random_key,
const char *repo_salt,
unsigned char *key_out, unsigned char *iv_out)
{
unsigned char key[32], iv[16];
seafile_derive_key (passwd, strlen(passwd), enc_version, repo_salt, key, iv);
if (enc_version == 1) {
memcpy (key_out, key, 16);
memcpy (iv_out, iv, 16);
return 0;
} else if (enc_version >= 2) {
unsigned char enc_random_key[48], *dec_random_key;
int outlen;
SeafileCrypt *crypt;
if (random_key == NULL || random_key[0] == 0) {
seaf_warning ("Empty random key.\n");
return -1;
}
hex_to_rawdata (random_key, enc_random_key, 48);
crypt = seafile_crypt_new (enc_version, key, iv);
if (seafile_decrypt ((char **)&dec_random_key, &outlen,
(char *)enc_random_key, 48,
crypt) < 0) {
seaf_warning ("Failed to decrypt random key.\n");
g_free (crypt);
return -1;
}
g_free (crypt);
seafile_derive_key ((char *)dec_random_key, 32, enc_version,
repo_salt,
key, iv);
memcpy (key_out, key, 32);
memcpy (iv_out, iv, 16);
g_free (dec_random_key);
return 0;
}
return -1;
}
int
seafile_update_random_key (const char *old_passwd, const char *old_random_key,
const char *new_passwd, char *new_random_key,
int enc_version, const char *repo_salt)
{
unsigned char key[32], iv[16];
unsigned char random_key_raw[48], *secret_key, *new_random_key_raw;
int secret_key_len, random_key_len;
SeafileCrypt *crypt;
/* First, use old_passwd to decrypt secret key from old_random_key. */
seafile_derive_key (old_passwd, strlen(old_passwd), enc_version,
repo_salt, key, iv);
hex_to_rawdata (old_random_key, random_key_raw, 48);
crypt = seafile_crypt_new (enc_version, key, iv);
if (seafile_decrypt ((char **)&secret_key, &secret_key_len,
(char *)random_key_raw, 48,
crypt) < 0) {
seaf_warning ("Failed to decrypt random key.\n");
g_free (crypt);
return -1;
}
g_free (crypt);
/* Second, use new_passwd to encrypt secret key. */
seafile_derive_key (new_passwd, strlen(new_passwd), enc_version,
repo_salt, key, iv);
crypt = seafile_crypt_new (enc_version, key, iv);
seafile_encrypt ((char **)&new_random_key_raw, &random_key_len,
(char *)secret_key, secret_key_len, crypt);
rawdata_to_hex (new_random_key_raw, new_random_key, 48);
g_free (secret_key);
g_free (new_random_key_raw);
g_free (crypt);
return 0;
}
#ifdef USE_GPL_CRYPTO
int
seafile_encrypt (char **data_out,
int *out_len,
const char *data_in,
const int in_len,
SeafileCrypt *crypt)
{
char *buf = NULL, *enc_buf = NULL;
int buf_size, remain;
guint8 padding;
gnutls_cipher_hd_t handle;
gnutls_datum_t key, iv;
int rc, ret = 0;
buf_size = BLK_SIZE * ((in_len / BLK_SIZE) + 1);
remain = buf_size - in_len;
buf = g_new (char, buf_size);
memcpy (buf, data_in, in_len);
padding = (guint8)remain;
memset (buf + in_len, padding, remain);
key.data = crypt->key;
key.size = sizeof(crypt->key);
iv.data = crypt->iv;
iv.size = sizeof(crypt->iv);
if (crypt->version == 1 || crypt->version == 3) {
seaf_warning ("Encrypted library version % is not supported.\n", crypt->version);
ret = -1;
goto out;
} else {
rc = gnutls_cipher_init (&handle, GNUTLS_CIPHER_AES_256_CBC, &key, &iv);
if (rc < 0) {
seaf_warning ("Failed to init cipher: %s\n", gnutls_strerror(rc));
ret = -1;
goto out;
}
}
enc_buf = g_new (char, buf_size);
rc = gnutls_cipher_encrypt2 (handle, buf, buf_size, enc_buf, buf_size);
if (rc < 0) {
seaf_warning ("Failed to encrypt: %s\n", gnutls_strerror(rc));
ret = -1;
gnutls_cipher_deinit (handle);
goto out;
}
gnutls_cipher_deinit (handle);
out:
g_free (buf);
if (ret < 0) {
g_free (enc_buf);
*data_out = NULL;
*out_len = -1;
} else {
*data_out = enc_buf;
*out_len = buf_size;
}
return ret;
}
int
seafile_decrypt (char **data_out,
int *out_len,
const char *data_in,
const int in_len,
SeafileCrypt *crypt)
{
char *dec_buf = NULL;
gnutls_cipher_hd_t handle;
gnutls_datum_t key, iv;
int rc, ret = 0;
guint8 padding;
int remain;
if (in_len <= 0 || in_len % BLK_SIZE != 0) {
seaf_warning ("Invalid encrypted buffer size.\n");
return -1;
}
key.data = crypt->key;
key.size = sizeof(crypt->key);
iv.data = crypt->iv;
iv.size = sizeof(crypt->iv);
if (crypt->version == 1 || crypt->version == 3) {
seaf_warning ("Encrypted library version %d is not supported.\n", crypt->version);
ret = -1;
goto out;
} else {
rc = gnutls_cipher_init (&handle, GNUTLS_CIPHER_AES_256_CBC, &key, &iv);
if (rc < 0) {
seaf_warning ("Failed to init cipher: %s\n", gnutls_strerror(rc));
ret = -1;
goto out;
}
}
dec_buf = g_new (char, in_len);
rc = gnutls_cipher_decrypt2 (handle, data_in, in_len, dec_buf, in_len);
if (rc < 0) {
seaf_warning ("Failed to decrypt data: %s\n", gnutls_strerror(rc));
ret = -1;
gnutls_cipher_deinit (handle);
goto out;
}
padding = dec_buf[in_len - 1];
remain = padding;
*out_len = (in_len - remain);
*data_out = dec_buf;
gnutls_cipher_deinit (handle);
out:
if (ret < 0) {
g_free (dec_buf);
*data_out = NULL;
*out_len = -1;
}
return ret;
}
#else
int
seafile_encrypt (char **data_out,
int *out_len,
const char *data_in,
const int in_len,
SeafileCrypt *crypt)
{
*data_out = NULL;
*out_len = -1;
/* check validation */
if ( data_in == NULL || in_len <= 0 || crypt == NULL) {
seaf_warning ("Invalid params.\n");
return -1;
}
EVP_CIPHER_CTX *ctx;
int ret;
int blks;
/* Prepare CTX for encryption. */
ctx = EVP_CIPHER_CTX_new ();
if (crypt->version == 1)
ret = EVP_EncryptInit_ex (ctx,
EVP_aes_128_cbc(), /* cipher mode */
NULL, /* engine, NULL for default */
crypt->key, /* derived key */
crypt->iv); /* initial vector */
else if (crypt->version == 3)
ret = EVP_EncryptInit_ex (ctx,
EVP_aes_128_ecb(), /* cipher mode */
NULL, /* engine, NULL for default */
crypt->key, /* derived key */
crypt->iv); /* initial vector */
else
ret = EVP_EncryptInit_ex (ctx,
EVP_aes_256_cbc(), /* cipher mode */
NULL, /* engine, NULL for default */
crypt->key, /* derived key */
crypt->iv); /* initial vector */
if (ret == ENC_FAILURE) {
EVP_CIPHER_CTX_free (ctx);
return -1;
}
/* Allocating output buffer. */
/*
For EVP symmetric encryption, padding is always used __even if__
data size is a multiple of block size, in which case the padding
length is the block size. so we have the following:
*/
blks = (in_len / BLK_SIZE) + 1;
*data_out = (char *)g_malloc (blks * BLK_SIZE);
if (*data_out == NULL) {
seaf_warning ("failed to allocate the output buffer.\n");
goto enc_error;
}
int update_len, final_len;
/* Do the encryption. */
ret = EVP_EncryptUpdate (ctx,
(unsigned char*)*data_out,
&update_len,
(unsigned char*)data_in,
in_len);
if (ret == ENC_FAILURE)
goto enc_error;
/* Finish the possible partial block. */
ret = EVP_EncryptFinal_ex (ctx,
(unsigned char*)*data_out + update_len,
&final_len);
*out_len = update_len + final_len;
/* out_len should be equal to the allocated buffer size. */
if (ret == ENC_FAILURE || *out_len != (blks * BLK_SIZE))
goto enc_error;
EVP_CIPHER_CTX_free (ctx);
return 0;
enc_error:
EVP_CIPHER_CTX_free (ctx);
*out_len = -1;
if (*data_out != NULL)
g_free (*data_out);
*data_out = NULL;
return -1;
}
int
seafile_decrypt (char **data_out,
int *out_len,
const char *data_in,
const int in_len,
SeafileCrypt *crypt)
{
*data_out = NULL;
*out_len = -1;
/* Check validation. Because padding is always used, in_len must
* be a multiple of BLK_SIZE */
if ( data_in == NULL || in_len <= 0 || in_len % BLK_SIZE != 0 ||
crypt == NULL) {
seaf_warning ("Invalid param(s).\n");
return -1;
}
EVP_CIPHER_CTX *ctx;
int ret;
/* Prepare CTX for decryption. */
ctx = EVP_CIPHER_CTX_new ();
if (crypt->version == 1)
ret = EVP_DecryptInit_ex (ctx,
EVP_aes_128_cbc(), /* cipher mode */
NULL, /* engine, NULL for default */
crypt->key, /* derived key */
crypt->iv); /* initial vector */
else if (crypt->version == 3)
ret = EVP_DecryptInit_ex (ctx,
EVP_aes_128_ecb(), /* cipher mode */
NULL, /* engine, NULL for default */
crypt->key, /* derived key */
crypt->iv); /* initial vector */
else
ret = EVP_DecryptInit_ex (ctx,
EVP_aes_256_cbc(), /* cipher mode */
NULL, /* engine, NULL for default */
crypt->key, /* derived key */
crypt->iv); /* initial vector */
if (ret == DEC_FAILURE) {
EVP_CIPHER_CTX_free (ctx);
return -1;
}
/* Allocating output buffer. */
*data_out = (char *)g_malloc (in_len);
if (*data_out == NULL) {
seaf_warning ("failed to allocate the output buffer.\n");
goto dec_error;
}
int update_len, final_len;
/* Do the decryption. */
ret = EVP_DecryptUpdate (ctx,
(unsigned char*)*data_out,
&update_len,
(unsigned char*)data_in,
in_len);
if (ret == DEC_FAILURE)
goto dec_error;
/* Finish the possible partial block. */
ret = EVP_DecryptFinal_ex (ctx,
(unsigned char*)*data_out + update_len,
&final_len);
*out_len = update_len + final_len;
/* out_len should be smaller than in_len. */
if (ret == DEC_FAILURE || *out_len > in_len)
goto dec_error;
EVP_CIPHER_CTX_free (ctx);
return 0;
dec_error:
EVP_CIPHER_CTX_free (ctx);
*out_len = -1;
if (*data_out != NULL)
g_free (*data_out);
*data_out = NULL;
return -1;
}
#endif /* USE_GPL_CRYPTO */
seadrive-fuse-3.0.13/src/seafile-crypt.h 0000664 0000000 0000000 00000007451 14761776747 0020110 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
Description:
The function pair "seafile_encrypt/seafile_decrypt" are used to
encrypt/decrypt data in the seafile system, using AES 128 bit ecb
algorithm provided by openssl.
*/
#ifndef _SEAFILE_CRYPT_H
#define _SEAFILE_CRYPT_H
#include
#include
/* Block size, in bytes. For AES it can only be 16 bytes. */
#define BLK_SIZE 16
#define ENCRYPT_BLK_SIZE BLK_SIZE
struct SeafileCrypt {
int version;
unsigned char key[32]; /* set when enc_version >= 1 */
unsigned char iv[16];
};
typedef struct SeafileCrypt SeafileCrypt;
SeafileCrypt *
seafile_crypt_new (int version, unsigned char *key, unsigned char *iv);
/*
Derive key and iv used by AES encryption from @data_in.
key and iv is 16 bytes for version 1, and 32 bytes for version 2.
@data_out: pointer to the output of the encrpyted/decrypted data,
whose content must be freed by g_free when not used.
@out_len: pointer to length of output, in bytes
@data_in: address of input buffer
@in_len: length of data to be encrpyted/decrypted, in bytes
@crypt: container of crypto info.
RETURN VALUES:
On success, 0 is returned, and the encrpyted/decrypted data is in
*data_out, with out_len set to its length. On failure, -1 is returned
and *data_out is set to NULL, with out_len set to -1;
*/
int
seafile_derive_key (const char *data_in, int in_len, int version,
const char *repo_salt,
unsigned char *key, unsigned char *iv);
/*
* Generate the real key used to encrypt data.
* The key 32 bytes long and encrpted with @passwd.
*/
int
seafile_generate_random_key (const char *passwd, char *random_key,
int version, const char *repo_salt);
void
seafile_generate_magic (int version, const char *repo_id,
const char *passwd,
const char *repo_salt,
char *magic);
void
seafile_generate_pwd_hash (int version,
const char *repo_id,
const char *passwd,
const char *repo_salt,
const char *algo,
const char *params_str,
char *pwd_hash);
int
seafile_verify_repo_passwd (const char *repo_id,
const char *passwd,
const char *magic,
int version,
const char *repo_salt);
int
seafile_pwd_hash_verify_repo_passwd (int version,
const char *repo_id,
const char *passwd,
const char *repo_salt,
const char *pwd_hash,
const char *algo,
const char *params_str);
int
seafile_decrypt_repo_enc_key (int enc_version,
const char *passwd, const char *random_key,
const char *repo_salt,
unsigned char *key_out, unsigned char *iv_out);
int
seafile_update_random_key (const char *old_passwd, const char *old_random_key,
const char *new_passwd, char *new_random_key,
int version, const char *repo_salt);
int
seafile_encrypt (char **data_out,
int *out_len,
const char *data_in,
const int in_len,
SeafileCrypt *crypt);
int
seafile_decrypt (char **data_out,
int *out_len,
const char *data_in,
const int in_len,
SeafileCrypt *crypt);
#endif /* _SEAFILE_CRYPT_H */
seadrive-fuse-3.0.13/src/seafile-error.h 0000664 0000000 0000000 00000001332 14761776747 0020070 0 ustar 00root root 0000000 0000000 #ifndef SEAFILE_ERROR_H
#define SEAFILE_ERROR_H
#define SEAF_ERR_GENERAL 500
#define SEAF_ERR_BAD_REPO 501
#define SEAF_ERR_BAD_COMMIT 502
#define SEAF_ERR_BAD_ARGS 503
#define SEAF_ERR_INTERNAL 504
#define SEAF_ERR_BAD_FILE 505
#define SEAF_ERR_BAD_RELAY 506
#define SEAF_ERR_LIST_COMMITS 507
#define SEAF_ERR_REPO_AUTH 508
#define SEAF_ERR_GC_NOT_STARTED 509
#define SEAF_ERR_MONITOR_NOT_CONNECTED 510
#define SEAF_ERR_BAD_DIR_ID 511
#define SEAF_ERR_NO_WORKTREE 512
#define SEAF_ERR_BAD_PEER_ID 513
#define SEAF_ERR_REPO_LOCKED 514
#define SEAF_ERR_DIR_MISSING 515
#define SEAF_ERR_PATH_NO_EXIST 516 /* the dir or file pointed by this path not exists */
#endif
seadrive-fuse-3.0.13/src/seafile-session.c 0000664 0000000 0000000 00000037335 14761776747 0020431 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#include "common.h"
#include
#include
#include
#include
#include
#include
#include
#include
#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
#include
#include
#include
#else
#include
#endif
#include
#include "utils.h"
#include "seafile-session.h"
#include "seafile-config.h"
#include "log.h"
#define MAX_THREADS 50
enum {
REPO_COMMITTED,
REPO_FETCHED,
REPO_UPLOADED,
REPO_HTTP_FETCHED,
REPO_HTTP_UPLOADED,
REPO_WORKTREE_CHECKED,
LAST_SIGNAL
};
int signals[LAST_SIGNAL];
G_DEFINE_TYPE (SeafileSession, seafile_session, G_TYPE_OBJECT);
static void
seafile_session_class_init (SeafileSessionClass *klass)
{
signals[REPO_COMMITTED] =
g_signal_new ("repo-committed", SEAFILE_TYPE_SESSION,
G_SIGNAL_RUN_LAST,
0, /* no class singal handler */
NULL, NULL, /* no accumulator */
g_cclosure_marshal_VOID__POINTER,
G_TYPE_NONE, 1, G_TYPE_POINTER);
signals[REPO_FETCHED] =
g_signal_new ("repo-fetched", SEAFILE_TYPE_SESSION,
G_SIGNAL_RUN_LAST,
0, /* no class singal handler */
NULL, NULL, /* no accumulator */
g_cclosure_marshal_VOID__POINTER,
G_TYPE_NONE, 1, G_TYPE_POINTER);
signals[REPO_UPLOADED] =
g_signal_new ("repo-uploaded", SEAFILE_TYPE_SESSION,
G_SIGNAL_RUN_LAST,
0, /* no class singal handler */
NULL, NULL, /* no accumulator */
g_cclosure_marshal_VOID__POINTER,
G_TYPE_NONE, 1, G_TYPE_POINTER);
signals[REPO_HTTP_FETCHED] =
g_signal_new ("repo-http-fetched", SEAFILE_TYPE_SESSION,
G_SIGNAL_RUN_LAST,
0, /* no class singal handler */
NULL, NULL, /* no accumulator */
g_cclosure_marshal_VOID__POINTER,
G_TYPE_NONE, 1, G_TYPE_POINTER);
signals[REPO_HTTP_UPLOADED] =
g_signal_new ("repo-http-uploaded", SEAFILE_TYPE_SESSION,
G_SIGNAL_RUN_LAST,
0, /* no class singal handler */
NULL, NULL, /* no accumulator */
g_cclosure_marshal_VOID__POINTER,
G_TYPE_NONE, 1, G_TYPE_POINTER);
}
static int
create_deleted_store_dirs (const char *deleted_store)
{
char *commits = NULL, *fs = NULL, *blocks = NULL;
int ret = 0;
if (checkdir_with_mkdir (deleted_store) < 0) {
seaf_warning ("Directory %s does not exist and is unable to create\n",
deleted_store);
return -1;
}
commits = g_build_filename (deleted_store, "commits", NULL);
if (checkdir_with_mkdir (commits) < 0) {
seaf_warning ("Directory %s does not exist and is unable to create\n",
commits);
ret = -1;
goto out;
}
fs = g_build_filename (deleted_store, "fs", NULL);
if (checkdir_with_mkdir (fs) < 0) {
seaf_warning ("Directory %s does not exist and is unable to create\n",
fs);
ret = -1;
goto out;
}
blocks = g_build_filename (deleted_store, "blocks", NULL);
if (checkdir_with_mkdir (blocks) < 0) {
seaf_warning ("Directory %s does not exist and is unable to create\n",
blocks);
ret = -1;
goto out;
}
out:
g_free (commits);
g_free (fs);
g_free (blocks);
return ret;
}
SeafileSession *
seafile_session_new(const char *seafile_dir)
{
char *abs_seafile_dir;
char *tmp_file_dir;
char *db_path;
char *deleted_store;
sqlite3 *config_db;
SeafileSession *session = NULL;
abs_seafile_dir = ccnet_expand_path (seafile_dir);
tmp_file_dir = g_build_filename (abs_seafile_dir, "tmpfiles", NULL);
db_path = g_build_filename (abs_seafile_dir, "config.db", NULL);
deleted_store = g_build_filename (abs_seafile_dir, "deleted_store", NULL);
if (checkdir_with_mkdir (abs_seafile_dir) < 0) {
seaf_warning ("Config dir %s does not exist and is unable to create\n",
abs_seafile_dir);
goto onerror;
}
if (checkdir_with_mkdir (tmp_file_dir) < 0) {
seaf_warning ("Temp file dir %s does not exist and is unable to create\n",
tmp_file_dir);
goto onerror;
}
if (create_deleted_store_dirs (deleted_store) < 0)
goto onerror;
config_db = seafile_session_config_open_db (db_path);
if (!config_db) {
seaf_warning ("Failed to open config db.\n");
goto onerror;
}
g_free (db_path);
session = g_object_new (SEAFILE_TYPE_SESSION, NULL);
session->ev_base = event_base_new ();
session->seaf_dir = abs_seafile_dir;
session->tmp_file_dir = tmp_file_dir;
session->config_db = config_db;
session->deleted_store = deleted_store;
session->fs_mgr = seaf_fs_manager_new (session, abs_seafile_dir);
if (!session->fs_mgr)
goto onerror;
session->block_mgr = seaf_block_manager_new (session, abs_seafile_dir);
if (!session->block_mgr)
goto onerror;
session->commit_mgr = seaf_commit_manager_new (session);
if (!session->commit_mgr)
goto onerror;
session->repo_mgr = seaf_repo_manager_new (session);
if (!session->repo_mgr)
goto onerror;
session->branch_mgr = seaf_branch_manager_new (session);
if (!session->branch_mgr)
goto onerror;
session->sync_mgr = seaf_sync_manager_new (session);
if (!session->sync_mgr)
goto onerror;
session->http_tx_mgr = http_tx_manager_new (session);
if (!session->http_tx_mgr)
goto onerror;
session->filelock_mgr = seaf_filelock_manager_new (session);
if (!session->filelock_mgr)
goto onerror;
session->journal_mgr = journal_manager_new (session);
if (!session->journal_mgr)
goto onerror;
session->file_cache_mgr = file_cache_mgr_new (abs_seafile_dir);
if (!session->file_cache_mgr)
goto onerror;
session->mq_mgr = mq_mgr_new ();
session->job_mgr = seaf_job_manager_new (session, MAX_THREADS);
#ifdef COMPILE_WS
session->notif_mgr = seaf_notif_manager_new (session);
#endif
return session;
onerror:
free (abs_seafile_dir);
g_free (tmp_file_dir);
g_free (db_path);
g_free (deleted_store);
g_free (session);
return NULL;
}
static void
seafile_session_init (SeafileSession *session)
{
}
static void
load_system_proxy (SeafileSession *session)
{
char *system_proxy_txt = g_build_filename (seaf->seaf_dir, "system-proxy.txt", NULL);
json_t *json = NULL;
if (!g_file_test (system_proxy_txt, G_FILE_TEST_EXISTS)) {
seaf_warning ("Can't load system proxy: file %s doesn't exist\n", system_proxy_txt);
goto out;
}
json_error_t jerror;
json = json_load_file(system_proxy_txt, 0, &jerror);
if (!json) {
if (strlen(jerror.text) > 0)
seaf_warning ("Failed to load system proxy information: %s.\n", jerror.text);
else
seaf_warning ("Failed to load system proxy information\n");
goto out;
}
const char *type;
type = json_object_get_string_member (json, "type");
if (!type) {
seaf_warning ("Failed to load system proxy information: proxy type missing\n");
goto out;
}
if (strcmp(type, "none") != 0 && strcmp(type, "socks") != 0 && strcmp(type, "http") != 0) {
seaf_warning ("Failed to load system proxy information: invalid proxy type %s\n", type);
goto out;
}
if (g_strcmp0(type, "none") == 0) {
goto out;
}
session->http_proxy_type = g_strdup(type);
session->http_proxy_addr = g_strdup(json_object_get_string_member (json, "addr"));
session->http_proxy_port = json_object_get_int_member (json, "port");
session->http_proxy_username = g_strdup(json_object_get_string_member (json, "username"));
session->http_proxy_password = g_strdup(json_object_get_string_member (json, "password"));
out:
g_free (system_proxy_txt);
if (json)
json_decref(json);
}
static char *
generate_client_id ()
{
char *uuid = gen_uuid();
unsigned char buf[20];
char sha1[41];
calculate_sha1 (buf, uuid, 20);
rawdata_to_hex (buf, sha1, 20);
g_free (uuid);
return g_strdup(sha1);
}
#define OFFICE_LOCK_PATTERN "~\\$(.+)$"
#define LIBRE_OFFICE_LOCK_PATTERN "\\.~lock\\.(.+)#$"
#define WPS_LOCK_PATTERN "\\.~(.+)$"
#define MAX_DELETED_FILES_NUM 500
void
seafile_session_prepare (SeafileSession *session)
{
/* load config */
session->client_id = seafile_session_config_get_string (session, KEY_CLIENT_ID);
if (!session->client_id) {
session->client_id = generate_client_id();
/*seafile_session_config_set_string (session,
KEY_CLIENT_ID,
session->client_id);*/
}
session->client_name = seafile_session_config_get_string (session, KEY_CLIENT_NAME);
if (!session->client_name) {
session->client_name = g_strdup("unknown");
}
session->sync_extra_temp_file = seafile_session_config_get_bool
(session, KEY_SYNC_EXTRA_TEMP_FILE);
/* Enable http sync by default. */
session->enable_http_sync = TRUE;
session->disable_verify_certificate = seafile_session_config_get_bool
(session, KEY_DISABLE_VERIFY_CERTIFICATE);
session->use_http_proxy =
seafile_session_config_get_bool(session, KEY_USE_PROXY);
gboolean use_system_proxy =
seafile_session_config_get_bool(session, "use_system_proxy");
if (use_system_proxy) {
load_system_proxy(session);
} else {
session->http_proxy_type =
seafile_session_config_get_string(session, KEY_PROXY_TYPE);
session->http_proxy_addr =
seafile_session_config_get_string(session, KEY_PROXY_ADDR);
session->http_proxy_port =
seafile_session_config_get_int(session, KEY_PROXY_PORT, NULL);
session->http_proxy_username =
seafile_session_config_get_string(session, KEY_PROXY_USERNAME);
session->http_proxy_password =
seafile_session_config_get_string(session, KEY_PROXY_PASSWORD);
}
session->enable_auto_lock = TRUE;
if (seafile_session_config_exists (session, KEY_ENABLE_AUTO_LOCK)) {
session->enable_auto_lock = seafile_session_config_get_bool
(session, KEY_ENABLE_AUTO_LOCK);
}
GError *error = NULL;
session->office_lock_file_regex = g_regex_new (OFFICE_LOCK_PATTERN,
0, 0, &error);
if (error) {
seaf_warning ("Failed to init office lock file pattern: %s.\n",
error->message);
g_regex_unref (session->office_lock_file_regex);
session->office_lock_file_regex = NULL;
g_clear_error (&error);
}
session->libre_office_lock_file_regex = g_regex_new (LIBRE_OFFICE_LOCK_PATTERN,
0, 0, &error);
if (error) {
seaf_warning ("Failed to init libre office lock file pattern: %s.\n",
error->message);
g_regex_unref (session->libre_office_lock_file_regex);
session->libre_office_lock_file_regex = NULL;
g_clear_error (&error);
}
session->wps_lock_file_regex = g_regex_new (WPS_LOCK_PATTERN,
0, 0, &error);
if (error) {
seaf_warning ("Failed to init wps lock file pattern: %s.\n",
error->message);
g_regex_unref (session->wps_lock_file_regex);
session->wps_lock_file_regex = NULL;
g_clear_error (&error);
}
session->delete_confirm_threshold = seafile_session_config_get_int(session, DELETE_CONFIRM_THRESHOLD, NULL);
if (session->delete_confirm_threshold <= 0) {
session->delete_confirm_threshold = MAX_DELETED_FILES_NUM;
}
session->hide_windows_incompatible_path_notification =
seafile_session_config_get_bool (session, KEY_HIDE_WINDOWS_INCOMPATIBLE_PATH_NOTIFICATION);
seaf_commit_manager_init (session->commit_mgr);
seaf_fs_manager_init (session->fs_mgr);
seaf_branch_manager_init (session->branch_mgr);
seaf_filelock_manager_init (session->filelock_mgr);
seaf_repo_manager_init (session->repo_mgr);
file_cache_mgr_init (session->file_cache_mgr);
seaf_sync_manager_init (session->sync_mgr);
mq_mgr_init (session->mq_mgr);
}
static gboolean
is_repo_store_in_use (const char *repo_id)
{
if (seaf_repo_manager_repo_exists (seaf->repo_mgr, repo_id))
return TRUE;
return FALSE;
}
static void
cleanup_unused_repo_stores (const char *type)
{
char *top_store_dir;
const char *repo_id;
top_store_dir = g_build_filename (seaf->seaf_dir, "storage", type, NULL);
GError *error = NULL;
GDir *dir = g_dir_open (top_store_dir, 0, &error);
if (!dir) {
seaf_warning ("Failed to open store dir %s: %s.\n",
top_store_dir, error->message);
g_free (top_store_dir);
return;
}
while ((repo_id = g_dir_read_name(dir)) != NULL) {
if (!is_repo_store_in_use (repo_id)) {
seaf_message ("Moving %s for deleted repo %s.\n", type, repo_id);
seaf_repo_manager_move_repo_store (seaf->repo_mgr, type, repo_id);
}
}
g_free (top_store_dir);
g_dir_close (dir);
}
static void *
on_start_cleanup_job (void *vdata)
{
cleanup_unused_repo_stores ("commits");
cleanup_unused_repo_stores ("fs");
cleanup_unused_repo_stores ("blocks");
return vdata;
}
static void
cleanup_job_done (void *vdata)
{
SeafileSession *session = vdata;
if (http_tx_manager_start (session->http_tx_mgr) < 0) {
g_error ("Failed to start http transfer manager.\n");
return;
}
if (seaf_sync_manager_start (session->sync_mgr) < 0) {
g_error ("Failed to start sync manager.\n");
return;
}
if (seaf_repo_manager_start (session->repo_mgr) < 0) {
g_error ("Failed to start repo manager.\n");
return;
}
if (seaf_filelock_manager_start (session->filelock_mgr) < 0) {
g_error ("Failed to start filelock manager.\n");
return;
}
file_cache_mgr_start (session->file_cache_mgr);
/* The system is up and running. */
session->started = TRUE;
}
static void
on_start_cleanup (SeafileSession *session)
{
seaf_job_manager_schedule_job (seaf->job_mgr,
on_start_cleanup_job,
cleanup_job_done,
session);
}
void
seafile_session_start (SeafileSession *session)
{
/* Finish cleanup task before anything is run. */
on_start_cleanup (session);
event_base_loop (session->ev_base, 0);
}
int
seafile_session_unmount (SeafileSession *session)
{
/* On Linux and Mac OS, the mount point is not unmounted. The GUI has to
* unmount the mount point before starting seadrive on next start up.
*/
GList *accounts = NULL, *ptr;
SeafAccount *account;
accounts = seaf_repo_manager_get_account_list (seaf->repo_mgr);
if (!accounts) {
return 0;
}
for (ptr = accounts; ptr; ptr = ptr->next) {
account = ptr->data;
seaf_repo_manager_flush_account_repo_journals (session->repo_mgr, account->server, account->username);
}
g_list_free_full (accounts, (GDestroyNotify)seaf_account_free);
return 0;
}
seadrive-fuse-3.0.13/src/seafile-session.h 0000664 0000000 0000000 00000006722 14761776747 0020432 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#ifndef SEAFILE_SESSION_H
#define SEAFILE_SESSION_H
#include
#include "job-mgr.h"
#include "db.h"
#include "block-mgr.h"
#include "fs-mgr.h"
#include "commit-mgr.h"
#include "branch-mgr.h"
#include "repo-mgr.h"
#include "sync-mgr.h"
#include "http-tx-mgr.h"
#include "filelock-mgr.h"
#include "file-cache-mgr.h"
#include "mq-mgr.h"
#include "notif-mgr.h"
#define SEAFILE_TYPE_SESSION (seafile_session_get_type ())
#define SEAFILE_SESSION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SEAFILE_TYPE_SESSION, SeafileSession))
#define SEAFILE_IS_SESSION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SEAFILE_TYPE_SESSION))
#define SEAFILE_SESSION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SEAFILE_TYPE_SESSION, SeafileSessionClass))
#define SEAFILE_IS_SESSION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SEAFILE_TYPE_SESSION))
#define SEAFILE_SESSION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SEAFILE_TYPE_SESSION, SeafileSessionClass))
typedef struct _SeafileSession SeafileSession;
typedef struct _SeafileSessionClass SeafileSessionClass;
struct event_base;
typedef enum SeafLang {
SEAF_LANG_EN_US = 0,
SEAF_LANG_ZH_CN,
SEAF_LANG_FR_FR,
SEAF_LANG_DE_DE,
} SeafLang;
struct _SeafileSession {
GObject parent_instance;
struct event_base *ev_base;
char *client_id;
char *client_name;
char *seaf_dir;
char *tmp_file_dir;
sqlite3 *config_db;
char *deleted_store;
char *rpc_socket_path;
char *mount_point;
SeafBlockManager *block_mgr;
SeafFSManager *fs_mgr;
SeafCommitManager *commit_mgr;
SeafBranchManager *branch_mgr;
SeafRepoManager *repo_mgr;
SeafSyncManager *sync_mgr;
SeafNotifManager *notif_mgr;
SeafJobManager *job_mgr;
HttpTxManager *http_tx_mgr;
SeafFilelockManager *filelock_mgr;
FileCacheMgr *file_cache_mgr;
JournalManager *journal_mgr;
MqMgr *mq_mgr;
/* Set after all components are up and running. */
gboolean started;
gboolean sync_extra_temp_file;
gboolean enable_http_sync;
gboolean disable_verify_certificate;
gboolean use_http_proxy;
char *http_proxy_type;
char *http_proxy_addr;
int http_proxy_port;
char *http_proxy_username;
char *http_proxy_password;
gboolean hide_windows_incompatible_path_notification;
gboolean enable_auto_lock;
GRegex *office_lock_file_regex;
GRegex *libre_office_lock_file_regex;
GRegex *wps_lock_file_regex;
gint64 last_check_repo_list_time;
gint64 last_access_fs_time;
int delete_confirm_threshold;
SeafLang language;
};
struct _SeafileSessionClass
{
GObjectClass parent_class;
};
extern SeafileSession *seaf;
SeafileSession *
seafile_session_new(const char *seafile_dir);
void
seafile_session_prepare (SeafileSession *session);
void
seafile_session_start (SeafileSession *session);
int
seafile_session_unmount (SeafileSession *session);
#endif /* SEAFILE_H */
seadrive-fuse-3.0.13/src/searpc-marshal.h 0000664 0000000 0000000 00000220707 14761776747 0020244 0 ustar 00root root 0000000 0000000
static char *
marshal_int__void (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
int ret = ((int (*)(GError **))func) (&error);
json_t *object = json_object ();
searpc_set_int_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_int__int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
int param1 = json_array_get_int_element (param_array, 1);
int ret = ((int (*)(int, GError **))func) (param1, &error);
json_t *object = json_object ();
searpc_set_int_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_int__int_int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
int param1 = json_array_get_int_element (param_array, 1);
int param2 = json_array_get_int_element (param_array, 2);
int ret = ((int (*)(int, int, GError **))func) (param1, param2, &error);
json_t *object = json_object ();
searpc_set_int_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_int__int_string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
int param1 = json_array_get_int_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
int ret = ((int (*)(int, const char*, GError **))func) (param1, param2, &error);
json_t *object = json_object ();
searpc_set_int_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_int__int_string_int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
int param1 = json_array_get_int_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
int param3 = json_array_get_int_element (param_array, 3);
int ret = ((int (*)(int, const char*, int, GError **))func) (param1, param2, param3, &error);
json_t *object = json_object ();
searpc_set_int_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_int__int_string_string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
int param1 = json_array_get_int_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
int ret = ((int (*)(int, const char*, const char*, GError **))func) (param1, param2, param3, &error);
json_t *object = json_object ();
searpc_set_int_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_int__int_string_int_int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
int param1 = json_array_get_int_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
int param3 = json_array_get_int_element (param_array, 3);
int param4 = json_array_get_int_element (param_array, 4);
int ret = ((int (*)(int, const char*, int, int, GError **))func) (param1, param2, param3, param4, &error);
json_t *object = json_object ();
searpc_set_int_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_int__int_int_string_string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
int param1 = json_array_get_int_element (param_array, 1);
int param2 = json_array_get_int_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
const char* param4 = json_array_get_string_or_null_element (param_array, 4);
int ret = ((int (*)(int, int, const char*, const char*, GError **))func) (param1, param2, param3, param4, &error);
json_t *object = json_object ();
searpc_set_int_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_int__string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
int ret = ((int (*)(const char*, GError **))func) (param1, &error);
json_t *object = json_object ();
searpc_set_int_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_int__string_int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
int param2 = json_array_get_int_element (param_array, 2);
int ret = ((int (*)(const char*, int, GError **))func) (param1, param2, &error);
json_t *object = json_object ();
searpc_set_int_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_int__string_int_int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
int param2 = json_array_get_int_element (param_array, 2);
int param3 = json_array_get_int_element (param_array, 3);
int ret = ((int (*)(const char*, int, int, GError **))func) (param1, param2, param3, &error);
json_t *object = json_object ();
searpc_set_int_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_int__string_int_string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
int param2 = json_array_get_int_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
int ret = ((int (*)(const char*, int, const char*, GError **))func) (param1, param2, param3, &error);
json_t *object = json_object ();
searpc_set_int_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_int__string_int_string_string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
int param2 = json_array_get_int_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
const char* param4 = json_array_get_string_or_null_element (param_array, 4);
int ret = ((int (*)(const char*, int, const char*, const char*, GError **))func) (param1, param2, param3, param4, &error);
json_t *object = json_object ();
searpc_set_int_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_int__string_int_int_string_string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
int param2 = json_array_get_int_element (param_array, 2);
int param3 = json_array_get_int_element (param_array, 3);
const char* param4 = json_array_get_string_or_null_element (param_array, 4);
const char* param5 = json_array_get_string_or_null_element (param_array, 5);
int ret = ((int (*)(const char*, int, int, const char*, const char*, GError **))func) (param1, param2, param3, param4, param5, &error);
json_t *object = json_object ();
searpc_set_int_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_int__string_string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
int ret = ((int (*)(const char*, const char*, GError **))func) (param1, param2, &error);
json_t *object = json_object ();
searpc_set_int_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_int__string_string_string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
int ret = ((int (*)(const char*, const char*, const char*, GError **))func) (param1, param2, param3, &error);
json_t *object = json_object ();
searpc_set_int_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_int__string_string_int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
int param3 = json_array_get_int_element (param_array, 3);
int ret = ((int (*)(const char*, const char*, int, GError **))func) (param1, param2, param3, &error);
json_t *object = json_object ();
searpc_set_int_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_int__string_string_string_int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
int param4 = json_array_get_int_element (param_array, 4);
int ret = ((int (*)(const char*, const char*, const char*, int, GError **))func) (param1, param2, param3, param4, &error);
json_t *object = json_object ();
searpc_set_int_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_int__string_string_string_string_int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
const char* param4 = json_array_get_string_or_null_element (param_array, 4);
int param5 = json_array_get_int_element (param_array, 5);
int ret = ((int (*)(const char*, const char*, const char*, const char*, int, GError **))func) (param1, param2, param3, param4, param5, &error);
json_t *object = json_object ();
searpc_set_int_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_int__string_string_string_string_string_int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
const char* param4 = json_array_get_string_or_null_element (param_array, 4);
const char* param5 = json_array_get_string_or_null_element (param_array, 5);
int param6 = json_array_get_int_element (param_array, 6);
int ret = ((int (*)(const char*, const char*, const char*, const char*, const char*, int, GError **))func) (param1, param2, param3, param4, param5, param6, &error);
json_t *object = json_object ();
searpc_set_int_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_int__string_string_int_int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
int param3 = json_array_get_int_element (param_array, 3);
int param4 = json_array_get_int_element (param_array, 4);
int ret = ((int (*)(const char*, const char*, int, int, GError **))func) (param1, param2, param3, param4, &error);
json_t *object = json_object ();
searpc_set_int_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_int__string_string_string_string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
const char* param4 = json_array_get_string_or_null_element (param_array, 4);
int ret = ((int (*)(const char*, const char*, const char*, const char*, GError **))func) (param1, param2, param3, param4, &error);
json_t *object = json_object ();
searpc_set_int_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_int__string_string_string_string_string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
const char* param4 = json_array_get_string_or_null_element (param_array, 4);
const char* param5 = json_array_get_string_or_null_element (param_array, 5);
int ret = ((int (*)(const char*, const char*, const char*, const char*, const char*, GError **))func) (param1, param2, param3, param4, param5, &error);
json_t *object = json_object ();
searpc_set_int_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_int__string_string_string_string_string_string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
const char* param4 = json_array_get_string_or_null_element (param_array, 4);
const char* param5 = json_array_get_string_or_null_element (param_array, 5);
const char* param6 = json_array_get_string_or_null_element (param_array, 6);
int ret = ((int (*)(const char*, const char*, const char*, const char*, const char*, const char*, GError **))func) (param1, param2, param3, param4, param5, param6, &error);
json_t *object = json_object ();
searpc_set_int_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_int__string_string_string_string_string_string_string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
const char* param4 = json_array_get_string_or_null_element (param_array, 4);
const char* param5 = json_array_get_string_or_null_element (param_array, 5);
const char* param6 = json_array_get_string_or_null_element (param_array, 6);
const char* param7 = json_array_get_string_or_null_element (param_array, 7);
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);
json_t *object = json_object ();
searpc_set_int_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_int__string_int64 (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
gint64 param2 = json_array_get_int_element (param_array, 2);
int ret = ((int (*)(const char*, gint64, GError **))func) (param1, param2, &error);
json_t *object = json_object ();
searpc_set_int_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_int__int_int64 (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
int param1 = json_array_get_int_element (param_array, 1);
gint64 param2 = json_array_get_int_element (param_array, 2);
int ret = ((int (*)(int, gint64, GError **))func) (param1, param2, &error);
json_t *object = json_object ();
searpc_set_int_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_int__int_string_int64 (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
int param1 = json_array_get_int_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
gint64 param3 = json_array_get_int_element (param_array, 3);
int ret = ((int (*)(int, const char*, gint64, GError **))func) (param1, param2, param3, &error);
json_t *object = json_object ();
searpc_set_int_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_int__int64 (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
gint64 param1 = json_array_get_int_element (param_array, 1);
int ret = ((int (*)(gint64, GError **))func) (param1, &error);
json_t *object = json_object ();
searpc_set_int_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_int64__void (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
gint64 ret = ((gint64 (*)(GError **))func) (&error);
json_t *object = json_object ();
searpc_set_int_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_int64__string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
gint64 ret = ((gint64 (*)(const char*, GError **))func) (param1, &error);
json_t *object = json_object ();
searpc_set_int_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_int64__int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
int param1 = json_array_get_int_element (param_array, 1);
gint64 ret = ((gint64 (*)(int, GError **))func) (param1, &error);
json_t *object = json_object ();
searpc_set_int_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_int64__int_string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
int param1 = json_array_get_int_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
gint64 ret = ((gint64 (*)(int, const char*, GError **))func) (param1, param2, &error);
json_t *object = json_object ();
searpc_set_int_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_int64__string_int_string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
int param2 = json_array_get_int_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
gint64 ret = ((gint64 (*)(const char*, int, const char*, GError **))func) (param1, param2, param3, &error);
json_t *object = json_object ();
searpc_set_int_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_string__void (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
char* ret = ((char* (*)(GError **))func) (&error);
json_t *object = json_object ();
searpc_set_string_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_string__int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
int param1 = json_array_get_int_element (param_array, 1);
char* ret = ((char* (*)(int, GError **))func) (param1, &error);
json_t *object = json_object ();
searpc_set_string_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_string__int_int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
int param1 = json_array_get_int_element (param_array, 1);
int param2 = json_array_get_int_element (param_array, 2);
char* ret = ((char* (*)(int, int, GError **))func) (param1, param2, &error);
json_t *object = json_object ();
searpc_set_string_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_string__int_string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
int param1 = json_array_get_int_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
char* ret = ((char* (*)(int, const char*, GError **))func) (param1, param2, &error);
json_t *object = json_object ();
searpc_set_string_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_string__int_int_string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
int param1 = json_array_get_int_element (param_array, 1);
int param2 = json_array_get_int_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
char* ret = ((char* (*)(int, int, const char*, GError **))func) (param1, param2, param3, &error);
json_t *object = json_object ();
searpc_set_string_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_string__string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
char* ret = ((char* (*)(const char*, GError **))func) (param1, &error);
json_t *object = json_object ();
searpc_set_string_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_string__string_int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
int param2 = json_array_get_int_element (param_array, 2);
char* ret = ((char* (*)(const char*, int, GError **))func) (param1, param2, &error);
json_t *object = json_object ();
searpc_set_string_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_string__string_int_int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
int param2 = json_array_get_int_element (param_array, 2);
int param3 = json_array_get_int_element (param_array, 3);
char* ret = ((char* (*)(const char*, int, int, GError **))func) (param1, param2, param3, &error);
json_t *object = json_object ();
searpc_set_string_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_string__string_string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
char* ret = ((char* (*)(const char*, const char*, GError **))func) (param1, param2, &error);
json_t *object = json_object ();
searpc_set_string_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_string__string_string_int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
int param3 = json_array_get_int_element (param_array, 3);
char* ret = ((char* (*)(const char*, const char*, int, GError **))func) (param1, param2, param3, &error);
json_t *object = json_object ();
searpc_set_string_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_string__string_string_int_int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
int param3 = json_array_get_int_element (param_array, 3);
int param4 = json_array_get_int_element (param_array, 4);
char* ret = ((char* (*)(const char*, const char*, int, int, GError **))func) (param1, param2, param3, param4, &error);
json_t *object = json_object ();
searpc_set_string_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_string__string_string_string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
char* ret = ((char* (*)(const char*, const char*, const char*, GError **))func) (param1, param2, param3, &error);
json_t *object = json_object ();
searpc_set_string_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_string__string_string_string_string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
const char* param4 = json_array_get_string_or_null_element (param_array, 4);
char* ret = ((char* (*)(const char*, const char*, const char*, const char*, GError **))func) (param1, param2, param3, param4, &error);
json_t *object = json_object ();
searpc_set_string_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_string__string_string_string_string_int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
const char* param4 = json_array_get_string_or_null_element (param_array, 4);
int param5 = json_array_get_int_element (param_array, 5);
char* ret = ((char* (*)(const char*, const char*, const char*, const char*, int, GError **))func) (param1, param2, param3, param4, param5, &error);
json_t *object = json_object ();
searpc_set_string_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_string__string_string_string_string_string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
const char* param4 = json_array_get_string_or_null_element (param_array, 4);
const char* param5 = json_array_get_string_or_null_element (param_array, 5);
char* ret = ((char* (*)(const char*, const char*, const char*, const char*, const char*, GError **))func) (param1, param2, param3, param4, param5, &error);
json_t *object = json_object ();
searpc_set_string_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_string__string_string_string_string_string_int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
const char* param4 = json_array_get_string_or_null_element (param_array, 4);
const char* param5 = json_array_get_string_or_null_element (param_array, 5);
int param6 = json_array_get_int_element (param_array, 6);
char* ret = ((char* (*)(const char*, const char*, const char*, const char*, const char*, int, GError **))func) (param1, param2, param3, param4, param5, param6, &error);
json_t *object = json_object ();
searpc_set_string_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_string__string_string_string_string_string_string_int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
const char* param4 = json_array_get_string_or_null_element (param_array, 4);
const char* param5 = json_array_get_string_or_null_element (param_array, 5);
const char* param6 = json_array_get_string_or_null_element (param_array, 6);
int param7 = json_array_get_int_element (param_array, 7);
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);
json_t *object = json_object ();
searpc_set_string_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_string__string_string_string_string_string_string_int_int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
const char* param4 = json_array_get_string_or_null_element (param_array, 4);
const char* param5 = json_array_get_string_or_null_element (param_array, 5);
const char* param6 = json_array_get_string_or_null_element (param_array, 6);
int param7 = json_array_get_int_element (param_array, 7);
int param8 = json_array_get_int_element (param_array, 8);
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);
json_t *object = json_object ();
searpc_set_string_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_string__string_string_string_string_string_string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
const char* param4 = json_array_get_string_or_null_element (param_array, 4);
const char* param5 = json_array_get_string_or_null_element (param_array, 5);
const char* param6 = json_array_get_string_or_null_element (param_array, 6);
char* ret = ((char* (*)(const char*, const char*, const char*, const char*, const char*, const char*, GError **))func) (param1, param2, param3, param4, param5, param6, &error);
json_t *object = json_object ();
searpc_set_string_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_string__string_string_string_string_string_string_int64 (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
const char* param4 = json_array_get_string_or_null_element (param_array, 4);
const char* param5 = json_array_get_string_or_null_element (param_array, 5);
const char* param6 = json_array_get_string_or_null_element (param_array, 6);
gint64 param7 = json_array_get_int_element (param_array, 7);
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);
json_t *object = json_object ();
searpc_set_string_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_string__string_string_string_string_string_string_int64_int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
const char* param4 = json_array_get_string_or_null_element (param_array, 4);
const char* param5 = json_array_get_string_or_null_element (param_array, 5);
const char* param6 = json_array_get_string_or_null_element (param_array, 6);
gint64 param7 = json_array_get_int_element (param_array, 7);
int param8 = json_array_get_int_element (param_array, 8);
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);
json_t *object = json_object ();
searpc_set_string_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_string__string_string_string_string_string_string_string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
const char* param4 = json_array_get_string_or_null_element (param_array, 4);
const char* param5 = json_array_get_string_or_null_element (param_array, 5);
const char* param6 = json_array_get_string_or_null_element (param_array, 6);
const char* param7 = json_array_get_string_or_null_element (param_array, 7);
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);
json_t *object = json_object ();
searpc_set_string_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_string__string_string_string_string_string_string_string_int64 (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
const char* param4 = json_array_get_string_or_null_element (param_array, 4);
const char* param5 = json_array_get_string_or_null_element (param_array, 5);
const char* param6 = json_array_get_string_or_null_element (param_array, 6);
const char* param7 = json_array_get_string_or_null_element (param_array, 7);
gint64 param8 = json_array_get_int_element (param_array, 8);
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);
json_t *object = json_object ();
searpc_set_string_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_string__string_string_string_string_string_string_string_string_string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
const char* param4 = json_array_get_string_or_null_element (param_array, 4);
const char* param5 = json_array_get_string_or_null_element (param_array, 5);
const char* param6 = json_array_get_string_or_null_element (param_array, 6);
const char* param7 = json_array_get_string_or_null_element (param_array, 7);
const char* param8 = json_array_get_string_or_null_element (param_array, 8);
const char* param9 = json_array_get_string_or_null_element (param_array, 9);
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);
json_t *object = json_object ();
searpc_set_string_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_string__string_int_string_string_string_string_string_string_string_string_string_string_int_string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
int param2 = json_array_get_int_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
const char* param4 = json_array_get_string_or_null_element (param_array, 4);
const char* param5 = json_array_get_string_or_null_element (param_array, 5);
const char* param6 = json_array_get_string_or_null_element (param_array, 6);
const char* param7 = json_array_get_string_or_null_element (param_array, 7);
const char* param8 = json_array_get_string_or_null_element (param_array, 8);
const char* param9 = json_array_get_string_or_null_element (param_array, 9);
const char* param10 = json_array_get_string_or_null_element (param_array, 10);
const char* param11 = json_array_get_string_or_null_element (param_array, 11);
const char* param12 = json_array_get_string_or_null_element (param_array, 12);
int param13 = json_array_get_int_element (param_array, 13);
const char* param14 = json_array_get_string_or_null_element (param_array, 14);
char* ret = ((char* (*)(const char*, int, const char*, const char*, const char*, 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, param12, param13, param14, &error);
json_t *object = json_object ();
searpc_set_string_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_string__string_int_string_int_int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
int param2 = json_array_get_int_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
int param4 = json_array_get_int_element (param_array, 4);
int param5 = json_array_get_int_element (param_array, 5);
char* ret = ((char* (*)(const char*, int, const char*, int, int, GError **))func) (param1, param2, param3, param4, param5, &error);
json_t *object = json_object ();
searpc_set_string_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_string__string_int_string_string_string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
int param2 = json_array_get_int_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
const char* param4 = json_array_get_string_or_null_element (param_array, 4);
const char* param5 = json_array_get_string_or_null_element (param_array, 5);
char* ret = ((char* (*)(const char*, int, const char*, const char*, const char*, GError **))func) (param1, param2, param3, param4, param5, &error);
json_t *object = json_object ();
searpc_set_string_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_objlist__void (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
GList* ret = ((GList* (*)(GError **))func) (&error);
json_t *object = json_object ();
searpc_set_objlist_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_objlist__int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
int param1 = json_array_get_int_element (param_array, 1);
GList* ret = ((GList* (*)(int, GError **))func) (param1, &error);
json_t *object = json_object ();
searpc_set_objlist_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_objlist__int_int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
int param1 = json_array_get_int_element (param_array, 1);
int param2 = json_array_get_int_element (param_array, 2);
GList* ret = ((GList* (*)(int, int, GError **))func) (param1, param2, &error);
json_t *object = json_object ();
searpc_set_objlist_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_objlist__int_string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
int param1 = json_array_get_int_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
GList* ret = ((GList* (*)(int, const char*, GError **))func) (param1, param2, &error);
json_t *object = json_object ();
searpc_set_objlist_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_objlist__int_int_int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
int param1 = json_array_get_int_element (param_array, 1);
int param2 = json_array_get_int_element (param_array, 2);
int param3 = json_array_get_int_element (param_array, 3);
GList* ret = ((GList* (*)(int, int, int, GError **))func) (param1, param2, param3, &error);
json_t *object = json_object ();
searpc_set_objlist_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_objlist__string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
GList* ret = ((GList* (*)(const char*, GError **))func) (param1, &error);
json_t *object = json_object ();
searpc_set_objlist_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_objlist__string_int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
int param2 = json_array_get_int_element (param_array, 2);
GList* ret = ((GList* (*)(const char*, int, GError **))func) (param1, param2, &error);
json_t *object = json_object ();
searpc_set_objlist_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_objlist__string_int_int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
int param2 = json_array_get_int_element (param_array, 2);
int param3 = json_array_get_int_element (param_array, 3);
GList* ret = ((GList* (*)(const char*, int, int, GError **))func) (param1, param2, param3, &error);
json_t *object = json_object ();
searpc_set_objlist_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_objlist__string_int_string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
int param2 = json_array_get_int_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
GList* ret = ((GList* (*)(const char*, int, const char*, GError **))func) (param1, param2, param3, &error);
json_t *object = json_object ();
searpc_set_objlist_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_objlist__string_string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
GList* ret = ((GList* (*)(const char*, const char*, GError **))func) (param1, param2, &error);
json_t *object = json_object ();
searpc_set_objlist_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_objlist__string_string_string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
GList* ret = ((GList* (*)(const char*, const char*, const char*, GError **))func) (param1, param2, param3, &error);
json_t *object = json_object ();
searpc_set_objlist_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_objlist__string_string_int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
int param3 = json_array_get_int_element (param_array, 3);
GList* ret = ((GList* (*)(const char*, const char*, int, GError **))func) (param1, param2, param3, &error);
json_t *object = json_object ();
searpc_set_objlist_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_objlist__string_string_string_int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
int param4 = json_array_get_int_element (param_array, 4);
GList* ret = ((GList* (*)(const char*, const char*, const char*, int, GError **))func) (param1, param2, param3, param4, &error);
json_t *object = json_object ();
searpc_set_objlist_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_objlist__string_string_int_int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
int param3 = json_array_get_int_element (param_array, 3);
int param4 = json_array_get_int_element (param_array, 4);
GList* ret = ((GList* (*)(const char*, const char*, int, int, GError **))func) (param1, param2, param3, param4, &error);
json_t *object = json_object ();
searpc_set_objlist_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_objlist__string_string_int_int_int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
int param3 = json_array_get_int_element (param_array, 3);
int param4 = json_array_get_int_element (param_array, 4);
int param5 = json_array_get_int_element (param_array, 5);
GList* ret = ((GList* (*)(const char*, const char*, int, int, int, GError **))func) (param1, param2, param3, param4, param5, &error);
json_t *object = json_object ();
searpc_set_objlist_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_objlist__int_string_string_int_int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
int param1 = json_array_get_int_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
int param4 = json_array_get_int_element (param_array, 4);
int param5 = json_array_get_int_element (param_array, 5);
GList* ret = ((GList* (*)(int, const char*, const char*, int, int, GError **))func) (param1, param2, param3, param4, param5, &error);
json_t *object = json_object ();
searpc_set_objlist_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_objlist__string_int_string_string_string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
int param2 = json_array_get_int_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
const char* param4 = json_array_get_string_or_null_element (param_array, 4);
const char* param5 = json_array_get_string_or_null_element (param_array, 5);
GList* ret = ((GList* (*)(const char*, int, const char*, const char*, const char*, GError **))func) (param1, param2, param3, param4, param5, &error);
json_t *object = json_object ();
searpc_set_objlist_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_objlist__string_int_string_int_int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
int param2 = json_array_get_int_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
int param4 = json_array_get_int_element (param_array, 4);
int param5 = json_array_get_int_element (param_array, 5);
GList* ret = ((GList* (*)(const char*, int, const char*, int, int, GError **))func) (param1, param2, param3, param4, param5, &error);
json_t *object = json_object ();
searpc_set_objlist_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_objlist__string_int_string_string_int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
int param2 = json_array_get_int_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
const char* param4 = json_array_get_string_or_null_element (param_array, 4);
int param5 = json_array_get_int_element (param_array, 5);
GList* ret = ((GList* (*)(const char*, int, const char*, const char*, int, GError **))func) (param1, param2, param3, param4, param5, &error);
json_t *object = json_object ();
searpc_set_objlist_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_objlist__string_string_string_string_int_int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
const char* param4 = json_array_get_string_or_null_element (param_array, 4);
int param5 = json_array_get_int_element (param_array, 5);
int param6 = json_array_get_int_element (param_array, 6);
GList* ret = ((GList* (*)(const char*, const char*, const char*, const char*, int, int, GError **))func) (param1, param2, param3, param4, param5, param6, &error);
json_t *object = json_object ();
searpc_set_objlist_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_object__void (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
GObject* ret = ((GObject* (*)(GError **))func) (&error);
json_t *object = json_object ();
searpc_set_object_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_object__int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
int param1 = json_array_get_int_element (param_array, 1);
GObject* ret = ((GObject* (*)(int, GError **))func) (param1, &error);
json_t *object = json_object ();
searpc_set_object_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_object__string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
GObject* ret = ((GObject* (*)(const char*, GError **))func) (param1, &error);
json_t *object = json_object ();
searpc_set_object_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_object__string_string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
GObject* ret = ((GObject* (*)(const char*, const char*, GError **))func) (param1, param2, &error);
json_t *object = json_object ();
searpc_set_object_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_object__string_string_string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
GObject* ret = ((GObject* (*)(const char*, const char*, const char*, GError **))func) (param1, param2, param3, &error);
json_t *object = json_object ();
searpc_set_object_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_object__string_int_string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
int param2 = json_array_get_int_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
GObject* ret = ((GObject* (*)(const char*, int, const char*, GError **))func) (param1, param2, param3, &error);
json_t *object = json_object ();
searpc_set_object_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_object__int_string_string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
int param1 = json_array_get_int_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
GObject* ret = ((GObject* (*)(int, const char*, const char*, GError **))func) (param1, param2, param3, &error);
json_t *object = json_object ();
searpc_set_object_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_object__string_string_string_string_string_string_string_int_int (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
const char* param2 = json_array_get_string_or_null_element (param_array, 2);
const char* param3 = json_array_get_string_or_null_element (param_array, 3);
const char* param4 = json_array_get_string_or_null_element (param_array, 4);
const char* param5 = json_array_get_string_or_null_element (param_array, 5);
const char* param6 = json_array_get_string_or_null_element (param_array, 6);
const char* param7 = json_array_get_string_or_null_element (param_array, 7);
int param8 = json_array_get_int_element (param_array, 8);
int param9 = json_array_get_int_element (param_array, 9);
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);
json_t *object = json_object ();
searpc_set_object_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_json__void (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
json_t* ret = ((json_t* (*)(GError **))func) (&error);
json_t *object = json_object ();
searpc_set_json_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static char *
marshal_json__string (void *func, json_t *param_array, gsize *ret_len)
{
GError *error = NULL;
const char* param1 = json_array_get_string_or_null_element (param_array, 1);
json_t* ret = ((json_t* (*)(const char*, GError **))func) (param1, &error);
json_t *object = json_object ();
searpc_set_json_to_ret_object (object, ret);
return searpc_marshal_set_ret_common (object, ret_len, error);
}
static void register_marshals(void)
{
{
searpc_server_register_marshal (searpc_signature_int__void(), marshal_int__void);
}
{
searpc_server_register_marshal (searpc_signature_int__int(), marshal_int__int);
}
{
searpc_server_register_marshal (searpc_signature_int__int_int(), marshal_int__int_int);
}
{
searpc_server_register_marshal (searpc_signature_int__int_string(), marshal_int__int_string);
}
{
searpc_server_register_marshal (searpc_signature_int__int_string_int(), marshal_int__int_string_int);
}
{
searpc_server_register_marshal (searpc_signature_int__int_string_string(), marshal_int__int_string_string);
}
{
searpc_server_register_marshal (searpc_signature_int__int_string_int_int(), marshal_int__int_string_int_int);
}
{
searpc_server_register_marshal (searpc_signature_int__int_int_string_string(), marshal_int__int_int_string_string);
}
{
searpc_server_register_marshal (searpc_signature_int__string(), marshal_int__string);
}
{
searpc_server_register_marshal (searpc_signature_int__string_int(), marshal_int__string_int);
}
{
searpc_server_register_marshal (searpc_signature_int__string_int_int(), marshal_int__string_int_int);
}
{
searpc_server_register_marshal (searpc_signature_int__string_int_string(), marshal_int__string_int_string);
}
{
searpc_server_register_marshal (searpc_signature_int__string_int_string_string(), marshal_int__string_int_string_string);
}
{
searpc_server_register_marshal (searpc_signature_int__string_int_int_string_string(), marshal_int__string_int_int_string_string);
}
{
searpc_server_register_marshal (searpc_signature_int__string_string(), marshal_int__string_string);
}
{
searpc_server_register_marshal (searpc_signature_int__string_string_string(), marshal_int__string_string_string);
}
{
searpc_server_register_marshal (searpc_signature_int__string_string_int(), marshal_int__string_string_int);
}
{
searpc_server_register_marshal (searpc_signature_int__string_string_string_int(), marshal_int__string_string_string_int);
}
{
searpc_server_register_marshal (searpc_signature_int__string_string_string_string_int(), marshal_int__string_string_string_string_int);
}
{
searpc_server_register_marshal (searpc_signature_int__string_string_string_string_string_int(), marshal_int__string_string_string_string_string_int);
}
{
searpc_server_register_marshal (searpc_signature_int__string_string_int_int(), marshal_int__string_string_int_int);
}
{
searpc_server_register_marshal (searpc_signature_int__string_string_string_string(), marshal_int__string_string_string_string);
}
{
searpc_server_register_marshal (searpc_signature_int__string_string_string_string_string(), marshal_int__string_string_string_string_string);
}
{
searpc_server_register_marshal (searpc_signature_int__string_string_string_string_string_string(), marshal_int__string_string_string_string_string_string);
}
{
searpc_server_register_marshal (searpc_signature_int__string_string_string_string_string_string_string(), marshal_int__string_string_string_string_string_string_string);
}
{
searpc_server_register_marshal (searpc_signature_int__string_int64(), marshal_int__string_int64);
}
{
searpc_server_register_marshal (searpc_signature_int__int_int64(), marshal_int__int_int64);
}
{
searpc_server_register_marshal (searpc_signature_int__int_string_int64(), marshal_int__int_string_int64);
}
{
searpc_server_register_marshal (searpc_signature_int__int64(), marshal_int__int64);
}
{
searpc_server_register_marshal (searpc_signature_int64__void(), marshal_int64__void);
}
{
searpc_server_register_marshal (searpc_signature_int64__string(), marshal_int64__string);
}
{
searpc_server_register_marshal (searpc_signature_int64__int(), marshal_int64__int);
}
{
searpc_server_register_marshal (searpc_signature_int64__int_string(), marshal_int64__int_string);
}
{
searpc_server_register_marshal (searpc_signature_int64__string_int_string(), marshal_int64__string_int_string);
}
{
searpc_server_register_marshal (searpc_signature_string__void(), marshal_string__void);
}
{
searpc_server_register_marshal (searpc_signature_string__int(), marshal_string__int);
}
{
searpc_server_register_marshal (searpc_signature_string__int_int(), marshal_string__int_int);
}
{
searpc_server_register_marshal (searpc_signature_string__int_string(), marshal_string__int_string);
}
{
searpc_server_register_marshal (searpc_signature_string__int_int_string(), marshal_string__int_int_string);
}
{
searpc_server_register_marshal (searpc_signature_string__string(), marshal_string__string);
}
{
searpc_server_register_marshal (searpc_signature_string__string_int(), marshal_string__string_int);
}
{
searpc_server_register_marshal (searpc_signature_string__string_int_int(), marshal_string__string_int_int);
}
{
searpc_server_register_marshal (searpc_signature_string__string_string(), marshal_string__string_string);
}
{
searpc_server_register_marshal (searpc_signature_string__string_string_int(), marshal_string__string_string_int);
}
{
searpc_server_register_marshal (searpc_signature_string__string_string_int_int(), marshal_string__string_string_int_int);
}
{
searpc_server_register_marshal (searpc_signature_string__string_string_string(), marshal_string__string_string_string);
}
{
searpc_server_register_marshal (searpc_signature_string__string_string_string_string(), marshal_string__string_string_string_string);
}
{
searpc_server_register_marshal (searpc_signature_string__string_string_string_string_int(), marshal_string__string_string_string_string_int);
}
{
searpc_server_register_marshal (searpc_signature_string__string_string_string_string_string(), marshal_string__string_string_string_string_string);
}
{
searpc_server_register_marshal (searpc_signature_string__string_string_string_string_string_int(), marshal_string__string_string_string_string_string_int);
}
{
searpc_server_register_marshal (searpc_signature_string__string_string_string_string_string_string_int(), marshal_string__string_string_string_string_string_string_int);
}
{
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);
}
{
searpc_server_register_marshal (searpc_signature_string__string_string_string_string_string_string(), marshal_string__string_string_string_string_string_string);
}
{
searpc_server_register_marshal (searpc_signature_string__string_string_string_string_string_string_int64(), marshal_string__string_string_string_string_string_string_int64);
}
{
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);
}
{
searpc_server_register_marshal (searpc_signature_string__string_string_string_string_string_string_string(), marshal_string__string_string_string_string_string_string_string);
}
{
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);
}
{
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);
}
{
searpc_server_register_marshal (searpc_signature_string__string_int_string_string_string_string_string_string_string_string_string_string_int_string(), marshal_string__string_int_string_string_string_string_string_string_string_string_string_string_int_string);
}
{
searpc_server_register_marshal (searpc_signature_string__string_int_string_int_int(), marshal_string__string_int_string_int_int);
}
{
searpc_server_register_marshal (searpc_signature_string__string_int_string_string_string(), marshal_string__string_int_string_string_string);
}
{
searpc_server_register_marshal (searpc_signature_objlist__void(), marshal_objlist__void);
}
{
searpc_server_register_marshal (searpc_signature_objlist__int(), marshal_objlist__int);
}
{
searpc_server_register_marshal (searpc_signature_objlist__int_int(), marshal_objlist__int_int);
}
{
searpc_server_register_marshal (searpc_signature_objlist__int_string(), marshal_objlist__int_string);
}
{
searpc_server_register_marshal (searpc_signature_objlist__int_int_int(), marshal_objlist__int_int_int);
}
{
searpc_server_register_marshal (searpc_signature_objlist__string(), marshal_objlist__string);
}
{
searpc_server_register_marshal (searpc_signature_objlist__string_int(), marshal_objlist__string_int);
}
{
searpc_server_register_marshal (searpc_signature_objlist__string_int_int(), marshal_objlist__string_int_int);
}
{
searpc_server_register_marshal (searpc_signature_objlist__string_int_string(), marshal_objlist__string_int_string);
}
{
searpc_server_register_marshal (searpc_signature_objlist__string_string(), marshal_objlist__string_string);
}
{
searpc_server_register_marshal (searpc_signature_objlist__string_string_string(), marshal_objlist__string_string_string);
}
{
searpc_server_register_marshal (searpc_signature_objlist__string_string_int(), marshal_objlist__string_string_int);
}
{
searpc_server_register_marshal (searpc_signature_objlist__string_string_string_int(), marshal_objlist__string_string_string_int);
}
{
searpc_server_register_marshal (searpc_signature_objlist__string_string_int_int(), marshal_objlist__string_string_int_int);
}
{
searpc_server_register_marshal (searpc_signature_objlist__string_string_int_int_int(), marshal_objlist__string_string_int_int_int);
}
{
searpc_server_register_marshal (searpc_signature_objlist__int_string_string_int_int(), marshal_objlist__int_string_string_int_int);
}
{
searpc_server_register_marshal (searpc_signature_objlist__string_int_string_string_string(), marshal_objlist__string_int_string_string_string);
}
{
searpc_server_register_marshal (searpc_signature_objlist__string_int_string_int_int(), marshal_objlist__string_int_string_int_int);
}
{
searpc_server_register_marshal (searpc_signature_objlist__string_int_string_string_int(), marshal_objlist__string_int_string_string_int);
}
{
searpc_server_register_marshal (searpc_signature_objlist__string_string_string_string_int_int(), marshal_objlist__string_string_string_string_int_int);
}
{
searpc_server_register_marshal (searpc_signature_object__void(), marshal_object__void);
}
{
searpc_server_register_marshal (searpc_signature_object__int(), marshal_object__int);
}
{
searpc_server_register_marshal (searpc_signature_object__string(), marshal_object__string);
}
{
searpc_server_register_marshal (searpc_signature_object__string_string(), marshal_object__string_string);
}
{
searpc_server_register_marshal (searpc_signature_object__string_string_string(), marshal_object__string_string_string);
}
{
searpc_server_register_marshal (searpc_signature_object__string_int_string(), marshal_object__string_int_string);
}
{
searpc_server_register_marshal (searpc_signature_object__int_string_string(), marshal_object__int_string_string);
}
{
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);
}
{
searpc_server_register_marshal (searpc_signature_json__void(), marshal_json__void);
}
{
searpc_server_register_marshal (searpc_signature_json__string(), marshal_json__string);
}
}
seadrive-fuse-3.0.13/src/searpc-signature.h 0000664 0000000 0000000 00000034641 14761776747 0020616 0 ustar 00root root 0000000 0000000
inline static gchar *
searpc_signature_int__void(void)
{
return searpc_compute_signature ("int", 0);
}
inline static gchar *
searpc_signature_int__int(void)
{
return searpc_compute_signature ("int", 1, "int");
}
inline static gchar *
searpc_signature_int__int_int(void)
{
return searpc_compute_signature ("int", 2, "int", "int");
}
inline static gchar *
searpc_signature_int__int_string(void)
{
return searpc_compute_signature ("int", 2, "int", "string");
}
inline static gchar *
searpc_signature_int__int_string_int(void)
{
return searpc_compute_signature ("int", 3, "int", "string", "int");
}
inline static gchar *
searpc_signature_int__int_string_string(void)
{
return searpc_compute_signature ("int", 3, "int", "string", "string");
}
inline static gchar *
searpc_signature_int__int_string_int_int(void)
{
return searpc_compute_signature ("int", 4, "int", "string", "int", "int");
}
inline static gchar *
searpc_signature_int__int_int_string_string(void)
{
return searpc_compute_signature ("int", 4, "int", "int", "string", "string");
}
inline static gchar *
searpc_signature_int__string(void)
{
return searpc_compute_signature ("int", 1, "string");
}
inline static gchar *
searpc_signature_int__string_int(void)
{
return searpc_compute_signature ("int", 2, "string", "int");
}
inline static gchar *
searpc_signature_int__string_int_int(void)
{
return searpc_compute_signature ("int", 3, "string", "int", "int");
}
inline static gchar *
searpc_signature_int__string_int_string(void)
{
return searpc_compute_signature ("int", 3, "string", "int", "string");
}
inline static gchar *
searpc_signature_int__string_int_string_string(void)
{
return searpc_compute_signature ("int", 4, "string", "int", "string", "string");
}
inline static gchar *
searpc_signature_int__string_int_int_string_string(void)
{
return searpc_compute_signature ("int", 5, "string", "int", "int", "string", "string");
}
inline static gchar *
searpc_signature_int__string_string(void)
{
return searpc_compute_signature ("int", 2, "string", "string");
}
inline static gchar *
searpc_signature_int__string_string_string(void)
{
return searpc_compute_signature ("int", 3, "string", "string", "string");
}
inline static gchar *
searpc_signature_int__string_string_int(void)
{
return searpc_compute_signature ("int", 3, "string", "string", "int");
}
inline static gchar *
searpc_signature_int__string_string_string_int(void)
{
return searpc_compute_signature ("int", 4, "string", "string", "string", "int");
}
inline static gchar *
searpc_signature_int__string_string_string_string_int(void)
{
return searpc_compute_signature ("int", 5, "string", "string", "string", "string", "int");
}
inline static gchar *
searpc_signature_int__string_string_string_string_string_int(void)
{
return searpc_compute_signature ("int", 6, "string", "string", "string", "string", "string", "int");
}
inline static gchar *
searpc_signature_int__string_string_int_int(void)
{
return searpc_compute_signature ("int", 4, "string", "string", "int", "int");
}
inline static gchar *
searpc_signature_int__string_string_string_string(void)
{
return searpc_compute_signature ("int", 4, "string", "string", "string", "string");
}
inline static gchar *
searpc_signature_int__string_string_string_string_string(void)
{
return searpc_compute_signature ("int", 5, "string", "string", "string", "string", "string");
}
inline static gchar *
searpc_signature_int__string_string_string_string_string_string(void)
{
return searpc_compute_signature ("int", 6, "string", "string", "string", "string", "string", "string");
}
inline static gchar *
searpc_signature_int__string_string_string_string_string_string_string(void)
{
return searpc_compute_signature ("int", 7, "string", "string", "string", "string", "string", "string", "string");
}
inline static gchar *
searpc_signature_int__string_int64(void)
{
return searpc_compute_signature ("int", 2, "string", "int64");
}
inline static gchar *
searpc_signature_int__int_int64(void)
{
return searpc_compute_signature ("int", 2, "int", "int64");
}
inline static gchar *
searpc_signature_int__int_string_int64(void)
{
return searpc_compute_signature ("int", 3, "int", "string", "int64");
}
inline static gchar *
searpc_signature_int__int64(void)
{
return searpc_compute_signature ("int", 1, "int64");
}
inline static gchar *
searpc_signature_int64__void(void)
{
return searpc_compute_signature ("int64", 0);
}
inline static gchar *
searpc_signature_int64__string(void)
{
return searpc_compute_signature ("int64", 1, "string");
}
inline static gchar *
searpc_signature_int64__int(void)
{
return searpc_compute_signature ("int64", 1, "int");
}
inline static gchar *
searpc_signature_int64__int_string(void)
{
return searpc_compute_signature ("int64", 2, "int", "string");
}
inline static gchar *
searpc_signature_int64__string_int_string(void)
{
return searpc_compute_signature ("int64", 3, "string", "int", "string");
}
inline static gchar *
searpc_signature_string__void(void)
{
return searpc_compute_signature ("string", 0);
}
inline static gchar *
searpc_signature_string__int(void)
{
return searpc_compute_signature ("string", 1, "int");
}
inline static gchar *
searpc_signature_string__int_int(void)
{
return searpc_compute_signature ("string", 2, "int", "int");
}
inline static gchar *
searpc_signature_string__int_string(void)
{
return searpc_compute_signature ("string", 2, "int", "string");
}
inline static gchar *
searpc_signature_string__int_int_string(void)
{
return searpc_compute_signature ("string", 3, "int", "int", "string");
}
inline static gchar *
searpc_signature_string__string(void)
{
return searpc_compute_signature ("string", 1, "string");
}
inline static gchar *
searpc_signature_string__string_int(void)
{
return searpc_compute_signature ("string", 2, "string", "int");
}
inline static gchar *
searpc_signature_string__string_int_int(void)
{
return searpc_compute_signature ("string", 3, "string", "int", "int");
}
inline static gchar *
searpc_signature_string__string_string(void)
{
return searpc_compute_signature ("string", 2, "string", "string");
}
inline static gchar *
searpc_signature_string__string_string_int(void)
{
return searpc_compute_signature ("string", 3, "string", "string", "int");
}
inline static gchar *
searpc_signature_string__string_string_int_int(void)
{
return searpc_compute_signature ("string", 4, "string", "string", "int", "int");
}
inline static gchar *
searpc_signature_string__string_string_string(void)
{
return searpc_compute_signature ("string", 3, "string", "string", "string");
}
inline static gchar *
searpc_signature_string__string_string_string_string(void)
{
return searpc_compute_signature ("string", 4, "string", "string", "string", "string");
}
inline static gchar *
searpc_signature_string__string_string_string_string_int(void)
{
return searpc_compute_signature ("string", 5, "string", "string", "string", "string", "int");
}
inline static gchar *
searpc_signature_string__string_string_string_string_string(void)
{
return searpc_compute_signature ("string", 5, "string", "string", "string", "string", "string");
}
inline static gchar *
searpc_signature_string__string_string_string_string_string_int(void)
{
return searpc_compute_signature ("string", 6, "string", "string", "string", "string", "string", "int");
}
inline static gchar *
searpc_signature_string__string_string_string_string_string_string_int(void)
{
return searpc_compute_signature ("string", 7, "string", "string", "string", "string", "string", "string", "int");
}
inline static gchar *
searpc_signature_string__string_string_string_string_string_string_int_int(void)
{
return searpc_compute_signature ("string", 8, "string", "string", "string", "string", "string", "string", "int", "int");
}
inline static gchar *
searpc_signature_string__string_string_string_string_string_string(void)
{
return searpc_compute_signature ("string", 6, "string", "string", "string", "string", "string", "string");
}
inline static gchar *
searpc_signature_string__string_string_string_string_string_string_int64(void)
{
return searpc_compute_signature ("string", 7, "string", "string", "string", "string", "string", "string", "int64");
}
inline static gchar *
searpc_signature_string__string_string_string_string_string_string_int64_int(void)
{
return searpc_compute_signature ("string", 8, "string", "string", "string", "string", "string", "string", "int64", "int");
}
inline static gchar *
searpc_signature_string__string_string_string_string_string_string_string(void)
{
return searpc_compute_signature ("string", 7, "string", "string", "string", "string", "string", "string", "string");
}
inline static gchar *
searpc_signature_string__string_string_string_string_string_string_string_int64(void)
{
return searpc_compute_signature ("string", 8, "string", "string", "string", "string", "string", "string", "string", "int64");
}
inline static gchar *
searpc_signature_string__string_string_string_string_string_string_string_string_string(void)
{
return searpc_compute_signature ("string", 9, "string", "string", "string", "string", "string", "string", "string", "string", "string");
}
inline static gchar *
searpc_signature_string__string_int_string_string_string_string_string_string_string_string_string_string_int_string(void)
{
return searpc_compute_signature ("string", 14, "string", "int", "string", "string", "string", "string", "string", "string", "string", "string", "string", "string", "int", "string");
}
inline static gchar *
searpc_signature_string__string_int_string_int_int(void)
{
return searpc_compute_signature ("string", 5, "string", "int", "string", "int", "int");
}
inline static gchar *
searpc_signature_string__string_int_string_string_string(void)
{
return searpc_compute_signature ("string", 5, "string", "int", "string", "string", "string");
}
inline static gchar *
searpc_signature_objlist__void(void)
{
return searpc_compute_signature ("objlist", 0);
}
inline static gchar *
searpc_signature_objlist__int(void)
{
return searpc_compute_signature ("objlist", 1, "int");
}
inline static gchar *
searpc_signature_objlist__int_int(void)
{
return searpc_compute_signature ("objlist", 2, "int", "int");
}
inline static gchar *
searpc_signature_objlist__int_string(void)
{
return searpc_compute_signature ("objlist", 2, "int", "string");
}
inline static gchar *
searpc_signature_objlist__int_int_int(void)
{
return searpc_compute_signature ("objlist", 3, "int", "int", "int");
}
inline static gchar *
searpc_signature_objlist__string(void)
{
return searpc_compute_signature ("objlist", 1, "string");
}
inline static gchar *
searpc_signature_objlist__string_int(void)
{
return searpc_compute_signature ("objlist", 2, "string", "int");
}
inline static gchar *
searpc_signature_objlist__string_int_int(void)
{
return searpc_compute_signature ("objlist", 3, "string", "int", "int");
}
inline static gchar *
searpc_signature_objlist__string_int_string(void)
{
return searpc_compute_signature ("objlist", 3, "string", "int", "string");
}
inline static gchar *
searpc_signature_objlist__string_string(void)
{
return searpc_compute_signature ("objlist", 2, "string", "string");
}
inline static gchar *
searpc_signature_objlist__string_string_string(void)
{
return searpc_compute_signature ("objlist", 3, "string", "string", "string");
}
inline static gchar *
searpc_signature_objlist__string_string_int(void)
{
return searpc_compute_signature ("objlist", 3, "string", "string", "int");
}
inline static gchar *
searpc_signature_objlist__string_string_string_int(void)
{
return searpc_compute_signature ("objlist", 4, "string", "string", "string", "int");
}
inline static gchar *
searpc_signature_objlist__string_string_int_int(void)
{
return searpc_compute_signature ("objlist", 4, "string", "string", "int", "int");
}
inline static gchar *
searpc_signature_objlist__string_string_int_int_int(void)
{
return searpc_compute_signature ("objlist", 5, "string", "string", "int", "int", "int");
}
inline static gchar *
searpc_signature_objlist__int_string_string_int_int(void)
{
return searpc_compute_signature ("objlist", 5, "int", "string", "string", "int", "int");
}
inline static gchar *
searpc_signature_objlist__string_int_string_string_string(void)
{
return searpc_compute_signature ("objlist", 5, "string", "int", "string", "string", "string");
}
inline static gchar *
searpc_signature_objlist__string_int_string_int_int(void)
{
return searpc_compute_signature ("objlist", 5, "string", "int", "string", "int", "int");
}
inline static gchar *
searpc_signature_objlist__string_int_string_string_int(void)
{
return searpc_compute_signature ("objlist", 5, "string", "int", "string", "string", "int");
}
inline static gchar *
searpc_signature_objlist__string_string_string_string_int_int(void)
{
return searpc_compute_signature ("objlist", 6, "string", "string", "string", "string", "int", "int");
}
inline static gchar *
searpc_signature_object__void(void)
{
return searpc_compute_signature ("object", 0);
}
inline static gchar *
searpc_signature_object__int(void)
{
return searpc_compute_signature ("object", 1, "int");
}
inline static gchar *
searpc_signature_object__string(void)
{
return searpc_compute_signature ("object", 1, "string");
}
inline static gchar *
searpc_signature_object__string_string(void)
{
return searpc_compute_signature ("object", 2, "string", "string");
}
inline static gchar *
searpc_signature_object__string_string_string(void)
{
return searpc_compute_signature ("object", 3, "string", "string", "string");
}
inline static gchar *
searpc_signature_object__string_int_string(void)
{
return searpc_compute_signature ("object", 3, "string", "int", "string");
}
inline static gchar *
searpc_signature_object__int_string_string(void)
{
return searpc_compute_signature ("object", 3, "int", "string", "string");
}
inline static gchar *
searpc_signature_object__string_string_string_string_string_string_string_int_int(void)
{
return searpc_compute_signature ("object", 9, "string", "string", "string", "string", "string", "string", "string", "int", "int");
}
inline static gchar *
searpc_signature_json__void(void)
{
return searpc_compute_signature ("json", 0);
}
inline static gchar *
searpc_signature_json__string(void)
{
return searpc_compute_signature ("json", 1, "string");
}
seadrive-fuse-3.0.13/src/set-perm.c 0000664 0000000 0000000 00000001753 14761776747 0017067 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#include "common.h"
#include "utils.h"
#define DEBUG_FLAG SEAFILE_DEBUG_SYNC
#include "log.h"
#include "set-perm.h"
#include
int
seaf_set_path_permission (const char *path, SeafPathPerm perm, gboolean recursive)
{
struct stat st;
mode_t new_mode;
if (stat (path, &st) < 0) {
seaf_warning ("Failed to stat %s: %s\n", path, strerror(errno));
return -1;
}
new_mode = st.st_mode;
if (perm == SEAF_PATH_PERM_RO)
new_mode &= ~(S_IWUSR);
else if (perm == SEAF_PATH_PERM_RW)
new_mode |= S_IWUSR;
if (chmod (path, new_mode) < 0) {
seaf_warning ("Failed to chmod %s to %d: %s\n", path, new_mode, strerror(errno));
return -1;
}
return 0;
}
int
seaf_unset_path_permission (const char *path, gboolean recursive)
{
return 0;
}
SeafPathPerm
seaf_get_path_permission (const char *path)
{
return SEAF_PATH_PERM_UNKNOWN;
}
seadrive-fuse-3.0.13/src/set-perm.h 0000664 0000000 0000000 00000000642 14761776747 0017070 0 ustar 00root root 0000000 0000000 #ifndef SEAF_SET_PERM_H
#define SEAF_SET_PERM_H
enum SeafPathPerm {
SEAF_PATH_PERM_UNKNOWN = 0,
SEAF_PATH_PERM_RO,
SEAF_PATH_PERM_RW,
};
typedef enum SeafPathPerm SeafPathPerm;
int
seaf_set_path_permission (const char *path, SeafPathPerm perm, gboolean recursive);
int
seaf_unset_path_permission (const char *path, gboolean recursive);
SeafPathPerm
seaf_get_path_permission (const char *path);
#endif
seadrive-fuse-3.0.13/src/sync-mgr.c 0000664 0000000 0000000 00000472756 14761776747 0017111 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#include "common.h"
#include
#include "db.h"
#include "seafile-session.h"
#include "seafile-config.h"
#include "sync-mgr.h"
#include "seafile-error.h"
#include "utils.h"
#include "timer.h"
#include "change-set.h"
#include "diff-simple.h"
#include "sync-status-tree.h"
#define DEBUG_FLAG SEAFILE_DEBUG_SYNC
#include "log.h"
#ifndef SEAFILE_CLIENT_VERSION
#define SEAFILE_CLIENT_VERSION PACKAGE_VERSION
#endif
#define DEFAULT_SYNC_INTERVAL 30 /* 30s */
#define CHECK_SYNC_INTERVAL 1000 /* 1s */
#define UPDATE_TX_STATE_INTERVAL 1000 /* 1s */
#define MAX_RUNNING_SYNC_TASKS 5
#define CHECK_SERVER_LOCKED_FILES_INTERVAL 10 /* 10s */
#define CHECK_LOCKED_FILES_INTERVAL 30 /* 30s */
#define CHECK_FOLDER_PERMS_INTERVAL 30 /* 30s */
#define MAX_RESYNC_COUNT 3
#define CHECK_REPO_LIST_INTERVAL 1 /* 1s */
#define JWT_TOKEN_EXPIRE_TIME 3*24*3600 /* 3 days */
struct _HttpServerState {
int http_version;
gboolean checking;
gint64 last_http_check_time;
char *testing_host;
/* Can be server_url or server_url:8082, depends on which one works. */
char *effective_host;
gboolean use_fileserver_port;
gboolean notif_server_checked;
gboolean notif_server_alive;
gboolean folder_perms_not_supported;
gint64 last_check_perms_time;
gboolean checking_folder_perms;
gboolean locked_files_not_supported;
gint64 last_check_locked_files_time;
gboolean checking_locked_files;
gboolean immediate_check_folder_perms;
gboolean immediate_check_locked_files;
gboolean is_fileserver_repo_list_api_disabled;
gint64 n_jwt_token_request;
};
typedef struct _HttpServerState HttpServerState;
struct _SyncTask;
struct _SyncInfo {
SeafSyncManager *manager;
char repo_id[37]; /* the repo */
struct _SyncTask *current_task;
RepoInfo *repo_info;
int resync_count;
gint64 last_sync_time;
gboolean in_sync; /* set to FALSE when sync state is DONE or ERROR */
gint err_cnt;
gboolean in_error; /* set to TRUE if err_cnt >= 3 */
gboolean del_confirmation_pending;
};
typedef struct _SyncInfo SyncInfo;
struct _RepoToken {
char token[41];
int repo_version;
};
typedef struct _RepoToken RepoToken;
enum {
SYNC_STATE_DONE,
SYNC_STATE_COMMIT,
SYNC_STATE_INIT,
SYNC_STATE_GET_TOKEN,
SYNC_STATE_FETCH,
SYNC_STATE_LOAD_REPO,
SYNC_STATE_UPLOAD,
SYNC_STATE_ERROR,
SYNC_STATE_CANCELED,
SYNC_STATE_CANCEL_PENDING,
SYNC_STATE_NUM,
};
enum {
SYNC_ERROR_NONE,
SYNC_ERROR_ACCESS_DENIED,
SYNC_ERROR_NO_WRITE_PERMISSION,
SYNC_ERROR_PERM_NOT_SYNCABLE,
SYNC_ERROR_QUOTA_FULL,
SYNC_ERROR_DATA_CORRUPT,
SYNC_ERROR_START_UPLOAD,
SYNC_ERROR_UPLOAD,
SYNC_ERROR_START_FETCH,
SYNC_ERROR_FETCH,
SYNC_ERROR_NOREPO,
SYNC_ERROR_REPO_CORRUPT,
SYNC_ERROR_COMMIT,
SYNC_ERROR_FILES_LOCKED,
SYNC_ERROR_GET_SYNC_INFO,
SYNC_ERROR_FILES_LOCKED_BY_USER,
SYNC_ERROR_FOLDER_PERM_DENIED,
SYNC_ERROR_DEL_CONFIRMATION_PENDING,
SYNC_ERROR_TOO_MANY_FILES,
SYNC_ERROR_BLOCK_MISSING,
SYNC_ERROR_UNKNOWN,
SYNC_ERROR_NUM,
};
struct _SyncTask {
SyncInfo *info;
HttpServerState *server_state;
int state;
int error;
int tx_error_code;
gboolean is_clone;
gboolean uploaded;
char *token;
char *password;
SeafRepo *repo; /* for convenience, only valid when in_sync. */
char *unsyncable_path;
char *server;
char *user;
};
typedef struct _SyncTask SyncTask;
struct _SyncError {
char *repo_id;
char *repo_name;
char *path;
int err_id;
gint64 timestamp;
};
typedef struct _SyncError SyncError;
typedef struct DelConfirmationResult {
gboolean resync;
} DelConfirmationResult;
struct _SeafSyncManagerPriv {
GHashTable *sync_infos;
pthread_mutex_t infos_lock;
GHashTable *repo_tokens;
int n_running_tasks;
GHashTable *http_server_states;
pthread_mutex_t server_states_lock;
struct SeafTimer *check_sync_timer;
struct SeafTimer *update_repo_list_timer;
struct SeafTimer *update_tx_state_timer;
struct SeafTimer *update_sync_status_timer;
GHashTable *active_paths;
pthread_mutex_t paths_lock;
GAsyncQueue *lock_file_job_queue;
GList *sync_errors;
pthread_mutex_t errors_lock;
gint server_disconnected;
GAsyncQueue *cache_file_task_queue;
gboolean auto_sync_enabled;
pthread_mutex_t del_confirmation_lock;
GHashTable *del_confirmation_tasks;
};
struct _ActivePathsInfo {
struct SyncStatusTree *syncing_tree;
struct SyncStatusTree *synced_tree;
};
typedef struct _ActivePathsInfo ActivePathsInfo;
struct _CacheFileTask {
char *repo_id;
char *path;
};
typedef struct _CacheFileTask CacheFileTask;
typedef struct SyncErrorInfo {
int error_id;
int error_level;
} SyncErrorInfo;
static SyncErrorInfo sync_error_info_tbl[] = {
{
SYNC_ERROR_ID_FILE_LOCKED_BY_APP,
SYNC_ERROR_LEVEL_FILE,
}, // 0
{
SYNC_ERROR_ID_FOLDER_LOCKED_BY_APP,
SYNC_ERROR_LEVEL_FILE,
}, // 1
{
SYNC_ERROR_ID_FILE_LOCKED,
SYNC_ERROR_LEVEL_FILE,
}, // 2
{
SYNC_ERROR_ID_INVALID_PATH,
SYNC_ERROR_LEVEL_FILE,
}, // 3
{
SYNC_ERROR_ID_INDEX_ERROR,
SYNC_ERROR_LEVEL_FILE,
}, // 4
{
SYNC_ERROR_ID_ACCESS_DENIED,
SYNC_ERROR_LEVEL_REPO,
}, // 5
{
SYNC_ERROR_ID_QUOTA_FULL,
SYNC_ERROR_LEVEL_REPO,
}, // 6
{
SYNC_ERROR_ID_NETWORK,
SYNC_ERROR_LEVEL_NETWORK,
}, // 7
{
SYNC_ERROR_ID_RESOLVE_PROXY,
SYNC_ERROR_LEVEL_NETWORK,
}, // 8
{
SYNC_ERROR_ID_RESOLVE_HOST,
SYNC_ERROR_LEVEL_NETWORK,
}, // 9
{
SYNC_ERROR_ID_CONNECT,
SYNC_ERROR_LEVEL_NETWORK,
}, // 10
{
SYNC_ERROR_ID_SSL,
SYNC_ERROR_LEVEL_NETWORK,
}, // 11
{
SYNC_ERROR_ID_TX,
SYNC_ERROR_LEVEL_NETWORK,
}, // 12
{
SYNC_ERROR_ID_TX_TIMEOUT,
SYNC_ERROR_LEVEL_NETWORK,
}, // 13
{
SYNC_ERROR_ID_UNHANDLED_REDIRECT,
SYNC_ERROR_LEVEL_NETWORK,
}, // 14
{
SYNC_ERROR_ID_SERVER,
SYNC_ERROR_LEVEL_REPO,
}, // 15
{
SYNC_ERROR_ID_LOCAL_DATA_CORRUPT,
SYNC_ERROR_LEVEL_REPO,
}, // 16
{
SYNC_ERROR_ID_WRITE_LOCAL_DATA,
SYNC_ERROR_LEVEL_REPO,
}, // 17
{
SYNC_ERROR_ID_PERM_NOT_SYNCABLE,
SYNC_ERROR_LEVEL_FILE,
}, // 18
{
SYNC_ERROR_ID_NO_WRITE_PERMISSION,
SYNC_ERROR_LEVEL_REPO,
}, // 19
{
SYNC_ERROR_ID_FOLDER_PERM_DENIED,
SYNC_ERROR_LEVEL_FILE,
}, // 20
{
SYNC_ERROR_ID_PATH_END_SPACE_PERIOD,
SYNC_ERROR_LEVEL_FILE,
}, // 21
{
SYNC_ERROR_ID_PATH_INVALID_CHARACTER,
SYNC_ERROR_LEVEL_FILE,
}, // 22
{
SYNC_ERROR_ID_UPDATE_TO_READ_ONLY_REPO,
SYNC_ERROR_LEVEL_FILE,
}, // 23
{
SYNC_ERROR_ID_CONFLICT,
SYNC_ERROR_LEVEL_FILE,
}, // 24
{
SYNC_ERROR_ID_UPDATE_NOT_IN_REPO,
SYNC_ERROR_LEVEL_FILE,
}, // 25
{
SYNC_ERROR_ID_LIBRARY_TOO_LARGE,
SYNC_ERROR_LEVEL_REPO,
}, // 26
{
SYNC_ERROR_ID_MOVE_NOT_IN_REPO,
SYNC_ERROR_LEVEL_FILE,
}, // 27
{
SYNC_ERROR_ID_DEL_CONFIRMATION_PENDING,
SYNC_ERROR_LEVEL_REPO,
}, // 28
{
SYNC_ERROR_ID_INVALID_PATH_ON_WINDOWS,
SYNC_ERROR_LEVEL_FILE,
}, // 29
{
SYNC_ERROR_ID_TOO_MANY_FILES,
SYNC_ERROR_LEVEL_REPO,
}, // 30
{
SYNC_ERROR_ID_BLOCK_MISSING,
SYNC_ERROR_LEVEL_REPO,
}, // 31
{
SYNC_ERROR_ID_CHECKOUT_FILE,
SYNC_ERROR_LEVEL_FILE,
}, // 32
{
SYNC_ERROR_ID_CASE_CONFLICT,
SYNC_ERROR_LEVEL_FILE,
}, // 33
{
SYNC_ERROR_ID_GENERAL_ERROR,
SYNC_ERROR_LEVEL_REPO,
}, // 34
{
SYNC_ERROR_ID_NO_ERROR,
SYNC_ERROR_LEVEL_REPO,
}, // 35
};
int
sync_error_level (int error)
{
g_return_val_if_fail ((error >= 0 && error < SYNC_ERROR_ID_NO_ERROR), -1);
return sync_error_info_tbl[error].error_level;
}
static const char *ignore_table[] = {
/* tmp files under Linux */
"*~",
/* Seafile's backup file */
"*.sbak",
/* Emacs tmp files */
"#*#",
/* windows image cache */
"Thumbs.db",
/* For Mac */
".DS_Store",
"._*",
".fuse_hidden*",
NULL,
};
static GPatternSpec **ignore_patterns;
static GPatternSpec* office_temp_ignore_patterns[5];
static int update_repo_list_pulse (void *vmanager);
static int auto_sync_pulse (void *vmanager);
static int update_tx_state_pulse (void *vmanager);
static SyncInfo*
get_sync_info (SeafSyncManager *manager, const char *repo_id)
{
SyncInfo *info;
pthread_mutex_lock (&manager->priv->infos_lock);
info = g_hash_table_lookup (manager->priv->sync_infos, repo_id);
if (info)
goto out;
info = g_new0 (SyncInfo, 1);
info->manager = manager;
memcpy (info->repo_id, repo_id, 36);
g_hash_table_insert (manager->priv->sync_infos, info->repo_id, info);
out:
pthread_mutex_unlock (&manager->priv->infos_lock);
return info;
}
SyncInfo *
seaf_sync_manager_get_sync_info (SeafSyncManager *mgr,
const char *repo_id)
{
return g_hash_table_lookup (mgr->priv->sync_infos, repo_id);
}
void
seaf_sync_manager_set_last_sync_time (SeafSyncManager *mgr,
const char *repo_id,
gint64 last_sync_time)
{
SyncInfo *info;
info = get_sync_info (mgr, repo_id);
info->last_sync_time = last_sync_time;
}
static inline gboolean
has_trailing_space_or_period (const char *path)
{
int len = strlen(path);
if (path[len - 1] == ' ' || path[len - 1] == '.') {
return TRUE;
}
return FALSE;
}
static gboolean
seaf_sync_manager_ignored_on_checkout_on_windows (const char *file_path, IgnoreReason *ignore_reason)
{
gboolean ret = FALSE;
static char illegals[] = {'\\', ':', '*', '?', '"', '<', '>', '|', '\b', '\t'};
char **components = g_strsplit (file_path, "/", -1);
int n_comps = g_strv_length (components);
int j = 0;
char *file_name;
int i;
char c;
for (; j < n_comps; ++j) {
file_name = components[j];
if (has_trailing_space_or_period (file_name)) {
/* Ignore files/dir whose path has trailing spaces. It would cause
* problem on windows. */
/* g_debug ("ignore '%s' which contains trailing space in path\n", path); */
ret = TRUE;
if (ignore_reason)
*ignore_reason = IGNORE_REASON_END_SPACE_PERIOD;
goto out;
}
for (i = 0; i < G_N_ELEMENTS(illegals); i++) {
if (strchr (file_name, illegals[i])) {
ret = TRUE;
if (ignore_reason)
*ignore_reason = IGNORE_REASON_INVALID_CHARACTER;
goto out;
}
}
for (c = 1; c <= 31; c++) {
if (strchr (file_name, c)) {
ret = TRUE;
if (ignore_reason)
*ignore_reason = IGNORE_REASON_INVALID_CHARACTER;
goto out;
}
}
}
out:
g_strfreev (components);
return ret;
}
static HttpServerState *
get_http_server_state (SeafSyncManagerPriv *priv, const char *fileserver_addr)
{
HttpServerState *state;
pthread_mutex_lock (&priv->server_states_lock);
state = g_hash_table_lookup (priv->http_server_states,
fileserver_addr);
pthread_mutex_unlock (&priv->server_states_lock);
if (state && state->http_version == 0) {
return NULL;
}
return state;
}
static const char *sync_state_str[] = {
"synchronized",
"committing",
"get sync info",
"get token",
"downloading",
"load repo",
"uploading",
"error",
"canceled",
"cancel pending"
};
static const char *sync_error_str[] = {
"Success",
"You do not have permission to access this library",
"Do not have write permission to the library",
"Do not have permission to sync the library",
"The storage space of the library owner has been used up",
"Internal data corrupted.",
"Failed to start upload.",
"Error occurred in upload.",
"Failed to start download.",
"Error occurred in download.",
"No such library on server.",
"Library is damaged on server.",
"Failed to index files.",
"Files are locked by other application",
"Failed to get sync info from server.",
"File is locked by another user.",
"Update to file denied by folder permission setting.",
"Waiting for confirmation to delete files.",
"Too many files in library.",
"Failed to upload file blocks.",
"Unknown error.",
};
static void
sync_task_free (SyncTask *task)
{
if (task->is_clone) {
g_free (task->server);
g_free (task->user);
}
g_free (task->token);
g_free (task);
}
static SyncTask *
sync_task_new (SyncInfo *info,
HttpServerState *server_state,
const char *server,
const char *user,
const char *token,
gboolean is_clone)
{
SyncTask *task = g_new0 (SyncTask, 1);
SeafRepo *repo = NULL;
if (!is_clone) {
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, info->repo_id);
if (!repo) {
seaf_warning ("Failed to get repo %s.\n", info->repo_id);
return NULL;
}
}
task->info = info;
task->server_state = server_state;
task->token = g_strdup(token);
task->is_clone = is_clone;
if (!is_clone)
task->repo = repo;
if (is_clone) {
task->server = g_strdup (server);
task->user = g_strdup (user);
}
info->in_sync = TRUE;
info->last_sync_time = time(NULL);
++(info->manager->priv->n_running_tasks);
/* Free the last task when a new task is started.
* This way we can always get the state of the last task even
* after it's done.
*/
if (task->info->current_task)
sync_task_free (task->info->current_task);
task->info->current_task = task;
return task;
}
static void
send_sync_error_notification (const char *repo_id,
const char *repo_name,
const char *path,
int err_id)
{
json_t *msg;
msg = json_object ();
json_object_set_new (msg, "type", json_string("sync.error"));
if (repo_id)
json_object_set_new (msg, "repo_id", json_string(repo_id));
if (repo_name)
json_object_set_new (msg, "repo_name", json_string(repo_name));
if (path)
json_object_set_new (msg, "path", json_string(path));
json_object_set_new (msg, "err_id", json_integer(err_id));
mq_mgr_push_msg (seaf->mq_mgr, SEADRIVE_NOTIFY_CHAN, msg);
}
static void
record_sync_error (SeafSyncManager *mgr,
const char *repo_id, const char *repo_name,
const char *path, int err_id)
{
GList *errors = mgr->priv->sync_errors, *ptr;
SyncError *err, *new_err;
gboolean found = FALSE;
pthread_mutex_lock (&mgr->priv->errors_lock);
for (ptr = errors; ptr; ptr = ptr->next) {
err = ptr->data;
if (g_strcmp0 (err->repo_id, repo_id) == 0 &&
g_strcmp0 (err->path, path) == 0) {
found = TRUE;
if (err->err_id != err_id) {
err->err_id = err_id;
send_sync_error_notification (repo_id, repo_name, path, err_id);
}
err->timestamp = (gint64)time(NULL);
break;
}
}
if (!found) {
new_err = g_new0 (SyncError, 1);
new_err->repo_id = g_strdup(repo_id);
new_err->repo_name = g_strdup(repo_name);
new_err->path = g_strdup(path);
new_err->err_id = err_id;
new_err->timestamp = (gint64)time(NULL);
mgr->priv->sync_errors = g_list_prepend (mgr->priv->sync_errors, new_err);
send_sync_error_notification (repo_id, repo_name, path, err_id);
}
pthread_mutex_unlock (&mgr->priv->errors_lock);
}
static void
remove_sync_error (SeafSyncManager *mgr, const char *repo_id, const char *path)
{
GList *ptr;
SyncError *err;
int err_level = -1;
pthread_mutex_lock (&mgr->priv->errors_lock);
for (ptr = mgr->priv->sync_errors; ptr; ptr = ptr->next) {
err = ptr->data;
err_level = sync_error_level (err->err_id);
// Even though the repo was successfully synced, a file-level sync error was displayed.
if (err_level == SYNC_ERROR_LEVEL_FILE) {
continue;
}
if (g_strcmp0 (err->repo_id, repo_id) == 0 &&
g_strcmp0 (err->path, path) == 0) {
mgr->priv->sync_errors = g_list_delete_link (mgr->priv->sync_errors, ptr);
g_free (err->repo_id);
g_free (err->repo_name);
g_free (err->path);
g_free (err);
break;
}
}
pthread_mutex_unlock (&mgr->priv->errors_lock);
}
json_t *
seaf_sync_manager_list_sync_errors (SeafSyncManager *mgr)
{
GList *ptr;
SyncError *err;
json_t *array, *obj;
array = json_array ();
pthread_mutex_lock (&mgr->priv->errors_lock);
for (ptr = mgr->priv->sync_errors; ptr; ptr = ptr->next) {
err = ptr->data;
obj = json_object ();
if (err->repo_id)
json_object_set_new (obj, "repo_id", json_string(err->repo_id));
if (err->repo_name)
json_object_set_new (obj, "repo_name", json_string(err->repo_name));
if (err->path)
json_object_set_new (obj, "path", json_string(err->path));
json_object_set_new (obj, "err_id", json_integer(err->err_id));
json_object_set_new (obj, "timestamp", json_integer(err->timestamp));
json_array_append_new (array, obj);
}
pthread_mutex_unlock (&mgr->priv->errors_lock);
return array;
}
static int
transfer_error_to_error_id (int http_tx_error)
{
switch (http_tx_error) {
case HTTP_TASK_ERR_NET:
return SYNC_ERROR_ID_NETWORK;
case HTTP_TASK_ERR_RESOLVE_PROXY:
return SYNC_ERROR_ID_RESOLVE_PROXY;
case HTTP_TASK_ERR_RESOLVE_HOST:
return SYNC_ERROR_ID_RESOLVE_HOST;
case HTTP_TASK_ERR_CONNECT:
return SYNC_ERROR_ID_CONNECT;
case HTTP_TASK_ERR_SSL:
return SYNC_ERROR_ID_SSL;
case HTTP_TASK_ERR_TX:
return SYNC_ERROR_ID_TX;
case HTTP_TASK_ERR_TX_TIMEOUT:
return SYNC_ERROR_ID_TX_TIMEOUT;
case HTTP_TASK_ERR_UNHANDLED_REDIRECT:
return SYNC_ERROR_ID_UNHANDLED_REDIRECT;
case HTTP_TASK_ERR_SERVER:
return SYNC_ERROR_ID_SERVER;
case HTTP_TASK_ERR_BAD_LOCAL_DATA:
return SYNC_ERROR_ID_LOCAL_DATA_CORRUPT;
case HTTP_TASK_ERR_WRITE_LOCAL_DATA:
return SYNC_ERROR_ID_WRITE_LOCAL_DATA;
case HTTP_TASK_ERR_LIBRARY_TOO_LARGE:
return SYNC_ERROR_ID_LIBRARY_TOO_LARGE;
default:
return SYNC_ERROR_ID_GENERAL_ERROR;
}
}
/* Check the notify setting by user. */
static gboolean
need_notify_sync (SeafRepo *repo)
{
char *notify_setting = seafile_session_config_get_string(seaf, "notify_sync");
if (notify_setting == NULL) {
seafile_session_config_set_string(seaf, "notify_sync", "on");
return TRUE;
}
gboolean result = (g_strcmp0(notify_setting, "on") == 0);
g_free (notify_setting);
return result;
}
static gboolean
find_meaningful_commit (SeafCommit *commit, void *data, gboolean *stop)
{
SeafCommit **p_head = data;
if (commit->second_parent_id && commit->new_merge && !commit->conflict)
return TRUE;
*stop = TRUE;
seaf_commit_ref (commit);
*p_head = commit;
return TRUE;
}
static void
notify_sync (SeafRepo *repo)
{
SeafCommit *head = NULL;
if (!seaf_commit_manager_traverse_commit_tree_truncated (seaf->commit_mgr,
repo->id, repo->version,
repo->head->commit_id,
find_meaningful_commit,
&head, FALSE)) {
seaf_warning ("Failed to traverse commit tree of %.8s.\n", repo->id);
return;
}
if (!head)
return;
json_t *msg = json_object ();
if (!repo->partial_commit_mode)
json_object_set_string_member (msg, "type", "sync.done");
else
json_object_set_string_member (msg, "type", "sync.multipart_upload");
json_object_set_string_member (msg, "repo_id", repo->id);
json_object_set_string_member (msg, "repo_name", repo->name);
json_object_set_string_member (msg, "commit_id", head->commit_id);
json_object_set_string_member (msg, "parent_commit_id", head->parent_id);
json_object_set_string_member (msg, "commit_desc", head->desc);
mq_mgr_push_msg (seaf->mq_mgr, SEADRIVE_NOTIFY_CHAN, msg);
seaf_commit_unref (head);
}
#define IN_ERROR_THRESHOLD 3
static gboolean
is_permanent_error (SyncTask *task)
{
if (task->error == SYNC_ERROR_ACCESS_DENIED ||
task->error == SYNC_ERROR_NO_WRITE_PERMISSION ||
task->error == SYNC_ERROR_FOLDER_PERM_DENIED ||
task->error == SYNC_ERROR_PERM_NOT_SYNCABLE ||
task->error == SYNC_ERROR_QUOTA_FULL ||
task->error == SYNC_ERROR_TOO_MANY_FILES ||
task->error == SYNC_ERROR_BLOCK_MISSING) {
return TRUE;
}
return FALSE;
}
static void
update_sync_info_error_state (SyncTask *task, int new_state)
{
SyncInfo *info = task->info;
if (new_state == SYNC_STATE_ERROR && is_permanent_error (task)) {
info->err_cnt++;
if (info->err_cnt >= IN_ERROR_THRESHOLD) {
info->in_error = TRUE;
/* Only delete a local repo after 3 consecutive permission errors.
* This prevents mistakenly re-syncing a repo on temporary database
* errors in the server.
*/
/* TODO: Currently, when there is a database error on the server when
* checking permissions, HTTP_FORBIDDEN is returned. On massive database
* errors, it'll cause all clients to un-sync all repos. To prevent
* confusing users, we don't un-sync repos automatically for the mean
* time.
*/
/* seaf_repo_manager_mark_repo_deleted (seaf->repo_mgr, task->repo, FALSE); */
}
} else if (info->err_cnt > 0) {
info->err_cnt = 0;
info->in_error = FALSE;
}
if (task->tx_error_code == HTTP_TASK_ERR_LIBRARY_TOO_LARGE)
info->in_error = TRUE;
}
static inline void
transition_sync_state (SyncTask *task, int new_state)
{
g_return_if_fail (new_state >= 0 && new_state < SYNC_STATE_NUM);
if (task->state != new_state) {
seaf_message ("Repo '%s' sync state transition from '%s' to '%s'.\n",
task->info->repo_info->name,
sync_state_str[task->state],
sync_state_str[new_state]);
if ((task->state == SYNC_STATE_INIT && task->uploaded &&
new_state == SYNC_STATE_DONE &&
need_notify_sync(task->repo)))
notify_sync (task->repo);
task->state = new_state;
if (new_state == SYNC_STATE_DONE ||
new_state == SYNC_STATE_CANCELED ||
new_state == SYNC_STATE_ERROR) {
task->info->in_sync = FALSE;
--(task->info->manager->priv->n_running_tasks);
update_sync_info_error_state (task, new_state);
if (new_state == SYNC_STATE_DONE)
remove_sync_error (seaf->sync_mgr, task->info->repo_info->id, NULL);
if (task->repo)
seaf_repo_unref (task->repo);
}
}
}
void
seaf_sync_manager_set_task_error (SyncTask *task, int error)
{
g_return_if_fail (error >= 0 && error < SYNC_ERROR_NUM);
if (task->state != SYNC_STATE_ERROR) {
seaf_message ("Repo '%s' sync state transition from %s to '%s': '%s'.\n",
task->info->repo_info->name,
sync_state_str[task->state],
sync_state_str[SYNC_STATE_ERROR],
sync_error_str[error]);
task->state = SYNC_STATE_ERROR;
task->error = error;
task->info->in_sync = FALSE;
--(task->info->manager->priv->n_running_tasks);
update_sync_info_error_state (task, SYNC_STATE_ERROR);
int sync_error_id;
if (task->error == SYNC_ERROR_ACCESS_DENIED)
sync_error_id = SYNC_ERROR_ID_ACCESS_DENIED;
else if (task->error == SYNC_ERROR_NO_WRITE_PERMISSION)
sync_error_id = SYNC_ERROR_ID_NO_WRITE_PERMISSION;
else if (task->error == SYNC_ERROR_PERM_NOT_SYNCABLE)
sync_error_id = SYNC_ERROR_ID_PERM_NOT_SYNCABLE;
else if (task->error == SYNC_ERROR_QUOTA_FULL)
sync_error_id = SYNC_ERROR_ID_QUOTA_FULL;
else if (task->error == SYNC_ERROR_DATA_CORRUPT)
sync_error_id = SYNC_ERROR_ID_LOCAL_DATA_CORRUPT;
else if (task->error == SYNC_ERROR_FILES_LOCKED_BY_USER)
sync_error_id = SYNC_ERROR_ID_FILE_LOCKED;
else if (task->error == SYNC_ERROR_FOLDER_PERM_DENIED)
sync_error_id = SYNC_ERROR_ID_FOLDER_PERM_DENIED;
else if (task->error == SYNC_ERROR_DEL_CONFIRMATION_PENDING)
sync_error_id = SYNC_ERROR_ID_DEL_CONFIRMATION_PENDING;
else if (task->error == SYNC_ERROR_TOO_MANY_FILES)
sync_error_id = SYNC_ERROR_ID_TOO_MANY_FILES;
else if (task->error == SYNC_ERROR_BLOCK_MISSING)
sync_error_id = SYNC_ERROR_ID_BLOCK_MISSING;
else if (task->tx_error_code > 0)
sync_error_id = transfer_error_to_error_id (task->tx_error_code);
else
sync_error_id = SYNC_ERROR_ID_GENERAL_ERROR;
record_sync_error (seaf->sync_mgr,
task->info->repo_info->id,
task->info->repo_info->name,
task->unsyncable_path,
sync_error_id);
/* If local metadata is corrupted, remove local repo and resync later. */
if (sync_error_id == SYNC_ERROR_ID_LOCAL_DATA_CORRUPT) {
if (task->info->resync_count < MAX_RESYNC_COUNT) {
seaf_message ("Repo %s(%s) local metadata is corrupted. Remove and resync later.\n",
task->repo->name, task->repo->id);
seaf_repo_manager_mark_repo_deleted (seaf->repo_mgr, task->repo, FALSE);
++(task->info->resync_count);
}
}
if (task->repo)
seaf_repo_unref (task->repo);
}
}
static void
active_paths_info_free (ActivePathsInfo *info);
SeafSyncManager*
seaf_sync_manager_new (SeafileSession *seaf)
{
SeafSyncManager *mgr = g_new0 (SeafSyncManager, 1);
mgr->priv = g_new0 (SeafSyncManagerPriv, 1);
mgr->seaf = seaf;
mgr->priv->sync_infos = g_hash_table_new (g_str_hash, g_str_equal);
pthread_mutex_init (&mgr->priv->infos_lock, NULL);
mgr->priv->repo_tokens = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, g_free);
mgr->priv->http_server_states = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, g_free);
pthread_mutex_init (&mgr->priv->server_states_lock, NULL);
gboolean exists;
int download_limit = seafile_session_config_get_int (seaf,
KEY_DOWNLOAD_LIMIT,
&exists);
if (exists)
mgr->download_limit = download_limit;
int upload_limit = seafile_session_config_get_int (seaf,
KEY_UPLOAD_LIMIT,
&exists);
if (exists)
mgr->upload_limit = upload_limit;
mgr->priv->active_paths = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free,
(GDestroyNotify)active_paths_info_free);
pthread_mutex_init (&mgr->priv->paths_lock, NULL);
ignore_patterns = g_new0 (GPatternSpec *, G_N_ELEMENTS(ignore_table));
int i;
for (i = 0; ignore_table[i] != NULL; i++) {
ignore_patterns[i] = g_pattern_spec_new (ignore_table[i]);
}
office_temp_ignore_patterns[0] = g_pattern_spec_new("~$*");
/* for files like ~WRL0001.tmp for docx and *.tmp for xlsx and pptx */
office_temp_ignore_patterns[1] = g_pattern_spec_new("*.tmp");
office_temp_ignore_patterns[2] = g_pattern_spec_new(".~lock*#");
/* for temporary files of WPS Office */
office_temp_ignore_patterns[3] = g_pattern_spec_new(".~*");
office_temp_ignore_patterns[4] = NULL;
mgr->priv->del_confirmation_tasks = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free,
g_free);
pthread_mutex_init (&mgr->priv->del_confirmation_lock, NULL);
mgr->priv->lock_file_job_queue = g_async_queue_new ();
pthread_mutex_init (&mgr->priv->errors_lock, NULL);
mgr->priv->cache_file_task_queue = g_async_queue_new ();
mgr->priv->auto_sync_enabled = TRUE;
return mgr;
}
int
seaf_sync_manager_init (SeafSyncManager *mgr)
{
return 0;
}
static void
on_repo_http_fetched (SeafileSession *seaf,
HttpTxTask *tx_task,
SeafSyncManager *mgr);
static void
on_repo_http_uploaded (SeafileSession *seaf,
HttpTxTask *tx_task,
SeafSyncManager *mgr);
static void* check_space_usage_thread (void *data);
static void* lock_file_worker (void *data);
static void* cache_file_task_worker (void *data);
int
seaf_sync_manager_start (SeafSyncManager *mgr)
{
mgr->priv->check_sync_timer = seaf_timer_new (
auto_sync_pulse, mgr, CHECK_SYNC_INTERVAL);
mgr->priv->update_repo_list_timer = seaf_timer_new (
update_repo_list_pulse, mgr, CHECK_REPO_LIST_INTERVAL*1000);
mgr->priv->update_tx_state_timer = seaf_timer_new (
update_tx_state_pulse, mgr, UPDATE_TX_STATE_INTERVAL);
g_signal_connect (seaf, "repo-http-fetched",
(GCallback)on_repo_http_fetched, mgr);
g_signal_connect (seaf, "repo-http-uploaded",
(GCallback)on_repo_http_uploaded, mgr);
pthread_t tid;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
int rc = pthread_create (&tid, &attr, check_space_usage_thread, NULL);
if (rc != 0) {
seaf_warning ("Failed to create check space thread: %s.\n", strerror(rc));
}
rc = pthread_create (&tid, &attr, lock_file_worker, mgr->priv->lock_file_job_queue);
if (rc != 0) {
seaf_warning ("Failed to create lock file thread: %s.\n", strerror(rc));
}
rc = pthread_create (&tid, NULL, cache_file_task_worker, mgr->priv->cache_file_task_queue);
if (rc != 0) {
seaf_warning ("Failed to create cache file task worker thread: %s.\n", strerror(rc));
}
return 0;
}
/* Synchronization framework. */
typedef struct _FolderPermInfo {
HttpServerState *server_state;
SyncTask *task;
char *server;
char *user;
} FolderPermInfo;
static void
folder_perm_info_free (FolderPermInfo *info)
{
if (!info)
return;
g_free (info->server);
g_free (info->user);
g_free (info);
}
static void
check_account_space_usage (SeafAccount *account)
{
gint64 total, used;
if (http_tx_manager_api_get_space_usage (seaf->http_tx_mgr,
account->server,
account->token,
&total, &used) < 0) {
seaf_warning ("Failed to get space usage for account %s/%s\n",
account->server, account->username);
return;
}
seaf_repo_manager_set_account_space (seaf->repo_mgr,
account->server, account->username,
total, used);
}
static void
check_accounts_space_usage ()
{
GList *accounts = NULL, *ptr;
SeafAccount *account;
accounts = seaf_repo_manager_get_account_list (seaf->repo_mgr);
if (!accounts)
return;
for (ptr = accounts; ptr; ptr = ptr->next) {
account = ptr->data;
check_account_space_usage ( account);
}
g_list_free_full (accounts, (GDestroyNotify)seaf_account_free);
}
static void*
check_space_usage_thread (void *data)
{
seaf_sleep (5);
while (1) {
check_accounts_space_usage ();
seaf_sleep (60);
}
return NULL;
}
static int
parse_repo_list (const char *rsp_content, int rsp_size, GHashTable *repos)
{
json_t *array = NULL, *object, *member;
json_error_t jerror;
size_t n;
int i;
RepoInfo *repo;
const char *repo_id, *head_commit_id, *name = NULL, *permission, *type_str, *owner;
gint64 mtime;
int version;
gboolean is_readonly, is_corrupted = FALSE;
RepoType type;
int ret = 0;
RepoInfo *repo_info = NULL;
array = json_loadb (rsp_content, rsp_size, 0, &jerror);
if (!array) {
seaf_warning ("Parse response failed: %s.\n", jerror.text);
return -1;
}
n = json_array_size (array);
for (i = 0; i < n; ++i) {
object = json_array_get (array, i);
member = json_object_get (object, "version");
if (!member) {
seaf_warning ("Invalid repo list response: no version.\n");
ret = -1;
goto out;
}
version = json_integer_value (member);
if (version == 0) {
continue;
}
member = json_object_get (object, "id");
if (!member) {
seaf_warning ("Invalid repo list response: no id.\n");
ret = -1;
goto out;
}
repo_id = json_string_value (member);
if (!is_uuid_valid (repo_id)) {
seaf_warning ("Invalid repo list response: invalid repo_id.\n");
ret = -1;
goto out;
}
member = json_object_get (object, "head_commit_id");
if (!member) {
seaf_warning ("Invalid repo list response: no head_commit_id.\n");
ret = -1;
goto out;
}
head_commit_id = json_string_value (member);
if (!is_object_id_valid (head_commit_id)) {
seaf_warning ("Invalid repo list response: invalid head_commit_id.\n");
ret = -1;
goto out;
}
member = json_object_get (object, "name");
if (!member) {
seaf_warning ("Invalid repo list response: no name.\n");
ret = -1;
goto out;
}
if (member == json_null()) {
is_corrupted = TRUE;
} else {
name = json_string_value (member);
}
member = json_object_get (object, "mtime");
if (!member) {
seaf_warning ("Invalid repo list response: no mtime.\n");
ret = -1;
goto out;
}
mtime = json_integer_value (member);
member = json_object_get (object, "permission");
if (!member) {
seaf_warning ("Invalid repo list response: no permission.\n");
ret = -1;
goto out;
}
permission = json_string_value (member);
if (g_strcmp0 (permission, "rw") == 0)
is_readonly = FALSE;
else
is_readonly = TRUE;
member = json_object_get (object, "owner");
if (!member) {
seaf_warning ("Invalid repo list response: no owner.\n");
ret = -1;
goto out;
}
owner = json_string_value (member);
member = json_object_get (object, "type");
if (!member) {
seaf_warning ("Invalid repo list response: no repo type.\n");
ret = -1;
goto out;
}
type_str = json_string_value (member);
if (g_strcmp0 (type_str, "repo") == 0) {
type = REPO_TYPE_MINE;
} else if (g_strcmp0 (type_str, "srepo") == 0) {
type = REPO_TYPE_SHARED;
} else if (g_strcmp0 (type_str, "grepo") == 0) {
if (g_strcmp0 (owner, "Organization") == 0)
type = REPO_TYPE_PUBLIC;
else
type = REPO_TYPE_GROUP;
} else {
seaf_warning ("Unknown repo type: %s.\n", type_str);
ret = -1;
goto out;
}
if ((repo_info = g_hash_table_lookup (repos, repo_id))) {
if (repo_info->type == REPO_TYPE_MINE ||
(!repo_info->is_readonly && is_readonly))
continue;
}
repo = repo_info_new (repo_id, head_commit_id, name, mtime, is_readonly);
repo->is_corrupted = is_corrupted;
repo->type = type;
g_hash_table_insert (repos, g_strdup(repo_id), repo);
}
out:
json_decref (array);
return ret;
}
#define HTTP_SERVERR_BAD_GATEWAY 502
#define HTTP_SERVERR_UNAVAILABLE 503
#define HTTP_SERVERR_TIMEOUT 504
static void update_current_repos(HttpAPIGetResult *result, void *user_data)
{
SeafAccount *account = user_data;
SeafAccount *curr_account;
GHashTable *repo_hash;
GList *added = NULL, *removed = NULL, *ptr;
RepoInfo *info;
SeafRepo *repo;
if (!result->success) {
if (result->http_status == HTTP_SERVERR_BAD_GATEWAY ||
result->http_status == HTTP_SERVERR_UNAVAILABLE ||
result->http_status == HTTP_SERVERR_TIMEOUT) {
seaf_repo_manager_set_account_server_disconnected (seaf->repo_mgr, account->server, account->username, TRUE);
}
record_sync_error (seaf->sync_mgr, NULL, NULL, NULL,
transfer_error_to_error_id (result->error_code));
g_atomic_int_set (&seaf->sync_mgr->priv->server_disconnected, 1);
return;
} else {
//If the fileserver hangs up before sending events to the notification server,
// the client will not receive the updates, and some updates will be lost.
// Therefore, after the fileserver recovers, immediately checking locks and folder perms.
if (account->server_disconnected) {
seaf_sync_manager_check_locks_and_folder_perms (seaf->sync_mgr, account->fileserver_addr);
}
remove_sync_error (seaf->sync_mgr, NULL, NULL);
g_atomic_int_set (&seaf->sync_mgr->priv->server_disconnected, 0);
seaf_repo_manager_set_account_server_disconnected (seaf->repo_mgr, account->server, account->username, FALSE);
}
/* If the get repo list request was sent around account deleting,
* this callback may be called after the account has been deleted.
*/
curr_account = seaf_repo_manager_get_account (seaf->repo_mgr, account->server, account->username);
if (!curr_account)
return;
seaf_account_free (curr_account);
repo_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, (GDestroyNotify)repo_info_free);
if (parse_repo_list (result->rsp_content, result->rsp_size, repo_hash) < 0) {
goto out;
}
seaf_repo_manager_set_repo_list_fetched (seaf->repo_mgr, account->server, account->username);
seaf_repo_manager_update_account_repos (seaf->repo_mgr, account->server, account->username, repo_hash, &added, &removed);
/* Mark repos removed on the server as delete-pending. */
for (ptr = removed; ptr; ptr = ptr->next) {
info = ptr->data;
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, info->id);
if (repo) {
seaf_message ("Repo %s(%.8s) was deleted on server. Remove local repo.\n",
repo->name, repo->id);
seaf_repo_manager_mark_repo_deleted (seaf->repo_mgr, repo, TRUE);
http_tx_manager_cancel_task (seaf->http_tx_mgr, info->id,
HTTP_TASK_TYPE_DOWNLOAD);
http_tx_manager_cancel_task (seaf->http_tx_mgr, info->id,
HTTP_TASK_TYPE_UPLOAD);
seaf_repo_unref (repo);
}
}
out:
g_list_free_full (added, (GDestroyNotify)repo_info_free);
g_list_free_full (removed, (GDestroyNotify)repo_info_free);
g_hash_table_destroy (repo_hash);
seaf_account_free (account);
}
static void
get_repo_list_cb (HttpAPIGetResult *result, void *user_data)
{
update_current_repos (result, user_data);
}
#define HTTP_FORBIDDEN 403
#define HTTP_NOT_FOUND 404
static void
fileserver_get_repo_list_cb (HttpAPIGetResult *result, void *user_data)
{
SeafAccount *account = user_data;
if (result->http_status == HTTP_NOT_FOUND ||
result->http_status == HTTP_FORBIDDEN) {
char *url = NULL;
if (result->http_status == HTTP_NOT_FOUND) {
HttpServerState *state;
state = get_http_server_state (seaf->sync_mgr->priv, account->fileserver_addr);
if (state)
state->is_fileserver_repo_list_api_disabled = TRUE;
}
// use seahub API
url = g_strdup_printf ("%s/api2/repos/", account->server);
if (http_tx_manager_api_get (seaf->http_tx_mgr,
account->server,
url,
account->token,
get_repo_list_cb,
account) < 0) {
seaf_account_free (account);
}
g_free (url);
return;
}
update_current_repos (result, user_data);
}
static int
update_account_repo_list (SeafSyncManager *manager,
const char *server,
const char *username)
{
HttpServerState *state;
char *url = NULL;
char *repo_id = NULL;
char *repo_token = NULL;
SeafAccount *account = seaf_repo_manager_get_account (seaf->repo_mgr, server, username);
if (!account)
return TRUE;
//get the first repo_id and repo_token from curr_repos which will be used to validate the user
repo_id = seaf_repo_manager_get_first_repo_token_from_account_repos (seaf->repo_mgr, server, username, &repo_token);
state = get_http_server_state (manager->priv, account->fileserver_addr);
if (!state || !repo_id || state->is_fileserver_repo_list_api_disabled) {
url = g_strdup_printf ("%s/api2/repos/", account->server);
if (http_tx_manager_api_get (seaf->http_tx_mgr,
account->server,
url,
account->token,
get_repo_list_cb,
account) < 0) {
seaf_warning ("Failed to start get repo list from server %s\n",
account->server);
seaf_account_free (account);
}
g_free (repo_id);
g_free (repo_token);
g_free (url);
return TRUE;
}
if (!state->use_fileserver_port)
url = g_strdup_printf ("%s/seafhttp/accessible-repos/?repo_id=%s", state->effective_host, repo_id);
else
url = g_strdup_printf ("%s/accessible-repos/?repo_id=%s", state->effective_host, repo_id);
if (http_tx_manager_fileserver_api_get (seaf->http_tx_mgr,
state->effective_host,
url,
repo_token,
fileserver_get_repo_list_cb,
account) < 0) {
seaf_warning ("Failed to start get repo list from server %s\n",
account->server);
seaf_account_free (account);
}
g_free (repo_id);
g_free (repo_token);
g_free (url);
return TRUE;
}
static int
update_repo_list_pulse (void *vmanager)
{
SeafSyncManager *manager = vmanager;
GList *accounts = NULL, *ptr;
SeafAccount *account;
accounts = seaf_repo_manager_get_account_list (seaf->repo_mgr);
if (!accounts)
return TRUE;
for (ptr = accounts; ptr; ptr = ptr->next) {
account = ptr->data;
update_account_repo_list (manager, account->server, account->username);
}
g_list_free_full (accounts, (GDestroyNotify)seaf_account_free);
return TRUE;
}
int
seaf_sync_manager_update_account_repo_list (SeafSyncManager *mgr,
const char *server,
const char *user)
{
return update_account_repo_list (mgr, server, user);
}
static void
check_folder_perms_done (HttpFolderPerms *result, void *user_data)
{
FolderPermInfo *perm_info = user_data;
HttpServerState *server_state = perm_info->server_state;
GList *ptr;
HttpFolderPermRes *res;
gint64 now = (gint64)time(NULL);
server_state->checking_folder_perms = FALSE;
if (!result->success) {
/* If on star-up we find that checking folder perms fails,
* we assume the server doesn't support it.
*/
if (server_state->last_check_perms_time == 0)
server_state->folder_perms_not_supported = TRUE;
server_state->last_check_perms_time = now;
folder_perm_info_free (perm_info);
return;
}
SyncInfo *info;
for (ptr = result->results; ptr; ptr = ptr->next) {
res = ptr->data;
info = get_sync_info (seaf->sync_mgr, res->repo_id);
// Getting folder perms before clone repo will set task, at this point we need to update the folder perms.
if (info->in_sync && !perm_info->task)
continue;
seaf_repo_manager_update_folder_perms (seaf->repo_mgr, res->repo_id,
FOLDER_PERM_TYPE_USER,
res->user_perms);
seaf_repo_manager_update_folder_perms (seaf->repo_mgr, res->repo_id,
FOLDER_PERM_TYPE_GROUP,
res->group_perms);
seaf_repo_manager_update_folder_perm_timestamp (seaf->repo_mgr,
res->repo_id,
res->timestamp);
}
server_state->last_check_perms_time = now;
folder_perm_info_free (perm_info);
}
static void
check_folder_permissions_immediately (SeafSyncManager *mgr,
HttpServerState *server_state,
const char *server,
const char *user,
gboolean force)
{
GList *repo_ids;
GList *ptr;
char *repo_id;
SeafRepo *repo;
gint64 timestamp;
HttpFolderPermReq *req;
GList *requests = NULL;
repo_ids = seaf_repo_manager_get_account_repo_ids (seaf->repo_mgr, server, user);
for (ptr = repo_ids; ptr; ptr = ptr->next) {
repo_id = ptr->data;
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo)
continue;
#ifdef COMPILE_WS
// Don't need to check folder perms regularly when we get folder perms from notification server.
if (!force && seaf_notif_manager_is_repo_subscribed (seaf->notif_mgr, repo)) {
seaf_repo_unref (repo);
continue;
}
#endif
if (!repo->token) {
seaf_repo_unref (repo);
continue;
}
timestamp = seaf_repo_manager_get_folder_perm_timestamp (seaf->repo_mgr,
repo->id);
if (timestamp < 0)
timestamp = 0;
req = g_new0 (HttpFolderPermReq, 1);
memcpy (req->repo_id, repo->id, 36);
req->token = g_strdup(repo->token);
req->timestamp = timestamp;
requests = g_list_append (requests, req);
seaf_repo_unref (repo);
}
g_list_free_full (repo_ids, g_free);
if (!requests)
return;
server_state->checking_folder_perms = TRUE;
FolderPermInfo *info = g_new0 (FolderPermInfo, 1);
info->server_state = server_state;
info->server = g_strdup (server);
info->user = g_strdup (user);
/* The requests list will be freed in http tx manager. */
if (http_tx_manager_get_folder_perms (seaf->http_tx_mgr,
server_state->effective_host,
server_state->use_fileserver_port,
requests,
check_folder_perms_done,
info) < 0) {
seaf_warning ("Failed to schedule check folder permissions\n");
server_state->checking_folder_perms = FALSE;
folder_perm_info_free (info);
}
}
static void
check_folder_permissions (SeafSyncManager *mgr,
HttpServerState *server_state,
const char *server,
const char *user)
{
gint64 now = (gint64)time(NULL);
if (server_state->http_version == 0 ||
server_state->folder_perms_not_supported ||
server_state->checking_folder_perms)
return;
if (server_state->immediate_check_folder_perms) {
server_state->immediate_check_folder_perms = FALSE;
check_folder_permissions_immediately (mgr, server_state, server, user, TRUE);
return;
}
if (server_state->last_check_perms_time > 0 &&
now - server_state->last_check_perms_time < CHECK_FOLDER_PERMS_INTERVAL)
return;
check_folder_permissions_immediately (mgr, server_state, server, user, FALSE);
}
static void
check_server_locked_files_done (HttpLockedFiles *result, void *user_data)
{
HttpServerState *server_state = user_data;
GList *ptr;
HttpLockedFilesRes *locked_res;
gint64 now = (gint64)time(NULL);
server_state->checking_locked_files = FALSE;
if (!result->success) {
/* If on star-up we find that checking locked files fails,
* we assume the server doesn't support it.
*/
if (server_state->last_check_locked_files_time == 0)
server_state->locked_files_not_supported = TRUE;
server_state->last_check_locked_files_time = now;
return;
}
SyncInfo *info;
for (ptr = result->results; ptr; ptr = ptr->next) {
locked_res = ptr->data;
info = get_sync_info (seaf->sync_mgr, locked_res->repo_id);
if (info->in_sync)
continue;
seaf_filelock_manager_update (seaf->filelock_mgr,
locked_res->repo_id,
locked_res->locked_files);
seaf_filelock_manager_update_timestamp (seaf->filelock_mgr,
locked_res->repo_id,
locked_res->timestamp);
}
server_state->last_check_locked_files_time = now;
}
static void
check_locked_files_immediately (SeafSyncManager *mgr, HttpServerState *server_state,
const char *server, const char *user, gboolean force)
{
GList *repo_ids;
GList *ptr;
char *repo_id;
SeafRepo *repo;
gint64 timestamp;
HttpLockedFilesReq *req;
GList *requests = NULL;
repo_ids = seaf_repo_manager_get_account_repo_ids (seaf->repo_mgr, server, user);
for (ptr = repo_ids; ptr; ptr = ptr->next) {
repo_id = ptr->data;
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo)
continue;
#ifdef COMPILE_WS
// Don't need to check locked files regularly when we get locked files from notification server.
if (!force && seaf_notif_manager_is_repo_subscribed (seaf->notif_mgr, repo)) {
seaf_repo_unref (repo);
continue;
}
#endif
if (!repo->token) {
seaf_repo_unref (repo);
continue;
}
timestamp = seaf_filelock_manager_get_timestamp (seaf->filelock_mgr,
repo->id);
if (timestamp < 0)
timestamp = 0;
req = g_new0 (HttpLockedFilesReq, 1);
memcpy (req->repo_id, repo->id, 36);
req->token = g_strdup(repo->token);
req->timestamp = timestamp;
requests = g_list_append (requests, req);
seaf_repo_unref (repo);
}
g_list_free_full (repo_ids, g_free);
if (!requests)
return;
server_state->checking_locked_files = TRUE;
/* The requests list will be freed in http tx manager. */
if (http_tx_manager_get_locked_files (seaf->http_tx_mgr,
server_state->effective_host,
server_state->use_fileserver_port,
requests,
check_server_locked_files_done,
server_state) < 0) {
seaf_warning ("Failed to schedule check server locked files\n");
server_state->checking_locked_files = FALSE;
}
}
static void
check_locked_files (SeafSyncManager *mgr, HttpServerState *server_state,
const char *server, const char *user)
{
gint64 now = (gint64)time(NULL);
if (server_state->http_version == 0 ||
server_state->locked_files_not_supported ||
server_state->checking_locked_files)
return;
if (server_state->immediate_check_locked_files) {
server_state->immediate_check_locked_files = FALSE;
check_locked_files_immediately (mgr, server_state, server, user, TRUE);
return;
}
if (server_state->last_check_locked_files_time > 0 &&
now - server_state->last_check_locked_files_time < CHECK_SERVER_LOCKED_FILES_INTERVAL)
return;
check_locked_files_immediately (mgr, server_state, server, user, FALSE);
}
static char *
http_fileserver_url (const char *url)
{
const char *host;
char *colon;
char *url_no_port;
char *ret = NULL;
/* Just return the url itself if it's invalid. */
if (strlen(url) <= strlen("http://"))
return g_strdup(url);
/* Skip protocol schem. */
host = url + strlen("http://");
colon = strrchr (host, ':');
if (colon) {
url_no_port = g_strndup(url, colon - url);
ret = g_strconcat(url_no_port, ":8082", NULL);
g_free (url_no_port);
} else {
ret = g_strconcat(url, ":8082", NULL);
}
return ret;
}
static void
check_http_fileserver_protocol_done (HttpProtocolVersion *result, void *user_data)
{
HttpServerState *state = user_data;
state->checking = FALSE;
if (result->check_success && !result->not_supported) {
state->http_version = result->version;
state->effective_host = http_fileserver_url(state->testing_host);
state->use_fileserver_port = TRUE;
}
}
static void
check_http_protocol_done (HttpProtocolVersion *result, void *user_data)
{
HttpServerState *state = user_data;
if (result->check_success && !result->not_supported) {
state->http_version = result->version;
state->effective_host = g_strdup(state->testing_host);
state->checking = FALSE;
} else if (strncmp(state->testing_host, "https", 5) != 0) {
char *host_fileserver = http_fileserver_url(state->testing_host);
if (http_tx_manager_check_protocol_version (seaf->http_tx_mgr,
host_fileserver,
TRUE,
check_http_fileserver_protocol_done,
state) < 0)
state->checking = FALSE;
g_free (host_fileserver);
} else {
state->checking = FALSE;
}
}
#define CHECK_HTTP_INTERVAL 10
/*
* Returns TRUE if we're ready to use http-sync; otherwise FALSE.
*/
static gboolean
check_http_protocol (SeafSyncManager *mgr, const char *server_url)
{
pthread_mutex_lock (&mgr->priv->server_states_lock);
HttpServerState *state = g_hash_table_lookup (mgr->priv->http_server_states,
server_url);
if (!state) {
state = g_new0 (HttpServerState, 1);
g_hash_table_insert (mgr->priv->http_server_states,
g_strdup(server_url), state);
}
pthread_mutex_unlock (&mgr->priv->server_states_lock);
if (state->checking) {
return FALSE;
}
if (state->http_version > 0) {
return TRUE;
}
/* If we haven't detected the server url successfully, retry every 10 seconds. */
gint64 now = time(NULL);
if (now - state->last_http_check_time < CHECK_HTTP_INTERVAL)
return FALSE;
/* First try server_url.
* If it fails and https is not used, try server_url:8082 instead.
*/
g_free (state->testing_host);
state->testing_host = g_strdup(server_url);
state->last_http_check_time = (gint64)time(NULL);
if (http_tx_manager_check_protocol_version (seaf->http_tx_mgr,
server_url,
FALSE,
check_http_protocol_done,
state) < 0)
return FALSE;
state->checking = TRUE;
return FALSE;
}
static void
check_notif_server_done (gboolean is_alive, void *user_data)
{
HttpServerState *state = user_data;
if (is_alive) {
state->notif_server_alive = TRUE;
seaf_message ("Notification server is enabled on the remote server %s.\n", state->effective_host);
}
}
static char *
http_notification_url (const char *url)
{
const char *host;
char *colon;
char *url_no_port;
char *ret = NULL;
/* Just return the url itself if it's invalid. */
if (strlen(url) <= strlen("http://"))
return g_strdup(url);
/* Skip protocol schem. */
host = url + strlen("http://");
colon = strrchr (host, ':');
if (colon) {
url_no_port = g_strndup(url, colon - url);
ret = g_strconcat(url_no_port, ":8083", NULL);
g_free (url_no_port);
} else {
ret = g_strconcat(url, ":8083", NULL);
}
return ret;
}
#ifdef COMPILE_WS
// Returns TRUE if notification server is alive; otherwise FALSE.
// We only check notification server once.
static gboolean
check_notif_server (SeafSyncManager *mgr, const char *server_url)
{
pthread_mutex_lock (&mgr->priv->server_states_lock);
HttpServerState *state = g_hash_table_lookup (mgr->priv->http_server_states,
server_url);
pthread_mutex_unlock (&mgr->priv->server_states_lock);
if (!state) {
return FALSE;
}
if (state->notif_server_alive) {
return TRUE;
}
if (state->notif_server_checked) {
return FALSE;
}
char *notif_url = NULL;
if (state->use_fileserver_port) {
notif_url = http_notification_url (server_url);
} else {
notif_url = g_strdup (server_url);
}
if (http_tx_manager_check_notif_server (seaf->http_tx_mgr,
notif_url,
state->use_fileserver_port,
check_notif_server_done,
state) < 0) {
g_free (notif_url);
return FALSE;
}
state->notif_server_checked = TRUE;
g_free (notif_url);
return FALSE;
}
#endif
gint
cmp_sync_info_by_sync_time (gconstpointer a, gconstpointer b, gpointer user_data)
{
const SyncInfo *info_a = a;
const SyncInfo *info_b = b;
return (info_a->last_sync_time - info_b->last_sync_time);
}
inline static gboolean
exceed_max_tasks (SeafSyncManager *manager)
{
if (manager->priv->n_running_tasks >= MAX_RUNNING_SYNC_TASKS)
return TRUE;
return FALSE;
}
static void
notify_fs_loaded ()
{
json_t *msg = json_object ();
json_object_set_string_member (msg, "type", "fs-loaded");
mq_mgr_push_msg (seaf->mq_mgr, SEADRIVE_NOTIFY_CHAN, msg);
seaf_message ("All repo fs trees are loaded.\n");
}
static char *
parse_jwt_token (const char *rsp_content, gint64 rsp_size)
{
json_t *object = NULL;
json_error_t jerror;
const char *member = NULL;
char *jwt_token = NULL;
object = json_loadb (rsp_content, rsp_size, 0, &jerror);
if (!object) {
return NULL;
}
if (json_object_has_member (object, "jwt_token")) {
member = json_object_get_string_member (object, "jwt_token");
if (member)
jwt_token = g_strdup (member);
} else {
json_decref (object);
return NULL;
}
json_decref (object);
return jwt_token;
}
#define HTTP_FORBIDDEN 403
#define HTTP_NOT_FOUND 404
#define HTTP_SERVERR 500
typedef struct _GetJwtTokenAux {
HttpServerState *state;
char *repo_id;
} GetJwtTokenAux;
static void
fileserver_get_jwt_token_cb (HttpAPIGetResult *result, void *user_data)
{
GetJwtTokenAux *aux = user_data;
HttpServerState *state = aux->state;
char *repo_id = aux->repo_id;
SeafRepo *repo = NULL;
char *jwt_token = NULL;
state->n_jwt_token_request--;
if (result->http_status == HTTP_NOT_FOUND ||
result->http_status == HTTP_FORBIDDEN ||
result->http_status == HTTP_SERVERR) {
goto out;
}
if (!result->success) {
goto out;
}
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo)
goto out;
jwt_token = parse_jwt_token (result->rsp_content,result->rsp_size);
if (!jwt_token) {
seaf_warning ("Failed to parse jwt token for repo %s\n", repo->id);
goto out;
}
g_free (repo->jwt_token);
repo->jwt_token = jwt_token;
out:
g_free (aux->repo_id);
g_free (aux);
seaf_repo_unref (repo);
return;
}
#ifdef COMPILE_WS
static int
check_and_subscribe_repo (HttpServerState *state, SeafRepo *repo)
{
char *url = NULL;
if (!state->notif_server_alive) {
return 0;
}
if (state->n_jwt_token_request > 10) {
return 0;
}
gint64 now = (gint64)time(NULL);
if (now - repo->last_check_jwt_token > JWT_TOKEN_EXPIRE_TIME) {
repo->last_check_jwt_token = now;
if (!state->use_fileserver_port)
url = g_strdup_printf ("%s/seafhttp/repo/%s/jwt-token", state->effective_host, repo->id);
else
url = g_strdup_printf ("%s/repo/%s/jwt-token", state->effective_host, repo->id);
state->n_jwt_token_request++;
GetJwtTokenAux *aux = g_new0 (GetJwtTokenAux, 1);
aux->repo_id = g_strdup (repo->id);
aux->state = state;
if (http_tx_manager_fileserver_api_get (seaf->http_tx_mgr,
state->effective_host,
url,
repo->token,
fileserver_get_jwt_token_cb,
aux) < 0) {
g_free (aux->repo_id);
g_free (aux);
state->n_jwt_token_request--;
}
g_free (url);
return 0;
}
if (!seaf_notif_manager_is_repo_subscribed (seaf->notif_mgr, repo)) {
if (repo->jwt_token)
seaf_notif_manager_subscribe_repo (seaf->notif_mgr, repo);
}
return 0;
}
#endif
static void
clone_repo (SeafSyncManager *manager,
SeafAccount *account,
HttpServerState *state,
SyncInfo *sync_info);
static int
sync_repo (SeafSyncManager *manager,
HttpServerState *state,
SyncInfo *sync_info,
SeafRepo *repo);
static int
auto_sync_account_repos (SeafSyncManager *manager, const char *server, const char *user)
{
SeafAccount *account;
GList *repo_info_list, *ptr, *sync_info_list = NULL;
RepoInfo *repo_info;
SyncInfo *sync_info;
SeafRepo *repo;
HttpServerState *state;
gboolean all_repos_loaded = TRUE;
if (!manager->priv->auto_sync_enabled)
return TRUE;
account = seaf_repo_manager_get_account (seaf->repo_mgr, server, user);
if (!account)
return TRUE;
if (!check_http_protocol (manager, account->fileserver_addr)) {
seaf_account_free (account);
return TRUE;
}
state = get_http_server_state (manager->priv, account->fileserver_addr);
if (!state) {
seaf_account_free (account);
return TRUE;
}
#ifdef COMPILE_WS
if (check_notif_server (manager, account->fileserver_addr)) {
seaf_notif_manager_connect_server (seaf->notif_mgr, account->fileserver_addr,
state->use_fileserver_port);
}
#endif
if (account->is_pro) {
check_folder_permissions (manager, state, account->server, account->username);
check_locked_files (manager, state, account->server, account->username);
}
/* Find sync_infos coresponded to repos in the current account. */
repo_info_list = seaf_repo_manager_get_account_repos (seaf->repo_mgr, server, user);
for (ptr = repo_info_list; ptr; ptr = ptr->next) {
repo_info = (RepoInfo *)ptr->data;
if (!account->all_repos_loaded) {
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_info->id);
if (!repo)
all_repos_loaded = FALSE;
else if (!repo->fs_ready) {
if (repo->encrypted && !repo->is_passwd_set) {
seaf_repo_unref (repo);
continue;
}
all_repos_loaded = FALSE;
}
seaf_repo_unref (repo);
}
/* The head commit id of the repo is fetched when getting repo list.
* If this is not set yet, we don't have enough information to sync
* the repo.
*/
if (!repo_info->head_commit_id) {
repo_info_free (repo_info);
continue;
}
if (repo_info->is_corrupted) {
repo_info_free (repo_info);
continue;
}
sync_info = get_sync_info (manager, repo_info->id);
if (sync_info->in_sync) {
repo_info_free (repo_info);
continue;
}
repo_info_free (sync_info->repo_info);
sync_info->repo_info = repo_info;
sync_info_list = g_list_prepend (sync_info_list, sync_info);
}
if (account->repo_list_fetched && !account->all_repos_loaded && all_repos_loaded) {
seaf_repo_manager_set_account_all_repos_loaded (seaf->repo_mgr, server, user);
notify_fs_loaded ();
}
/* Sort sync_infos by last_sync_time, so that we don't "starve" any repo. */
sync_info_list = g_list_sort_with_data (sync_info_list,
cmp_sync_info_by_sync_time,
NULL);
for (ptr = sync_info_list; ptr != NULL; ptr = ptr->next) {
sync_info = (SyncInfo *)ptr->data;
if (sync_info->in_error) {
continue;
}
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, sync_info->repo_id);
if (!repo) {
if (exceed_max_tasks (manager))
continue;
/* Don't re-download a repo that's still marked as delete-pending.
* It should be downloaded after the local repo is removed.
*/
if (seaf_repo_manager_is_repo_delete_pending (seaf->repo_mgr,
sync_info->repo_id))
continue;
seaf_message ("Cloning repo %s(%s).\n",
sync_info->repo_info->name, sync_info->repo_id);
clone_repo (manager, account, state, sync_info);
continue;
}
#ifdef USE_GPL_CRYPTO
if (repo->version == 0 || (repo->encrypted && repo->enc_version < 2)) {
seaf_repo_unref (repo);
continue;
}
#endif
if (!repo->token) {
/* If the user has logged out of the account, the repo token would
* be null */
seaf_repo_unref (repo);
continue;
}
/* If repo tree is not loaded yet, we should wait until it's loaded. */
if (!repo->fs_ready) {
seaf_repo_unref (repo);
continue;
}
if (exceed_max_tasks (manager) && !repo->force_sync_pending) {
seaf_repo_unref (repo);
continue;
}
if (repo->encrypted && !repo->is_passwd_set) {
seaf_repo_unref (repo);
continue;
}
sync_repo (manager, state, sync_info, repo);
#ifdef COMPILE_WS
check_and_subscribe_repo (state, repo);
#endif
seaf_repo_unref (repo);
}
seaf_account_free (account);
g_list_free (repo_info_list);
g_list_free (sync_info_list);
return TRUE;
}
static int
auto_sync_pulse (void *vmanager)
{
SeafSyncManager *manager = vmanager;
GList *accounts = NULL, *ptr;
SeafAccount *account;
accounts = seaf_repo_manager_get_account_list (seaf->repo_mgr);
if (!accounts)
return TRUE;
for (ptr = accounts; ptr; ptr = ptr->next) {
account = ptr->data;
auto_sync_account_repos (manager, account->server, account->username);
}
g_list_free_full (accounts, (GDestroyNotify)seaf_account_free);
return TRUE;
}
static void
check_repo_folder_perms_done (HttpFolderPerms *result, void *user_data)
{
FolderPermInfo *perm_info = user_data;
HttpServerState *server_state = perm_info->server_state;
SyncTask *task = perm_info->task;
SyncInfo *sync_info = task->info;
check_folder_perms_done (result, user_data);
if (!result->success) {
seaf_sync_manager_set_task_error (task, SYNC_ERROR_FETCH);
return;
}
RepoToken *repo_token = g_hash_table_lookup (seaf->sync_mgr->priv->repo_tokens,
sync_info->repo_id);
if (!repo_token) {
seaf_warning ("Failed to get reop sync token fro %s.\n", sync_info->repo_id);
seaf_sync_manager_set_task_error (task, SYNC_ERROR_FETCH);
return;
}
int rc = http_tx_manager_add_download (seaf->http_tx_mgr,
sync_info->repo_id,
repo_token->repo_version,
task->server,
task->user,
server_state->effective_host,
repo_token->token,
sync_info->repo_info->head_commit_id,
TRUE,
server_state->http_version,
server_state->use_fileserver_port,
NULL);
if (rc < 0) {
seaf_warning ("Failed to add download task for repo %s from %s.\n",
sync_info->repo_id, server_state->effective_host);
seaf_sync_manager_set_task_error (task, SYNC_ERROR_FETCH);
return;
}
transition_sync_state (task, SYNC_STATE_FETCH);
}
static void
check_folder_permissions_by_repo (HttpServerState *server_state,
const char *server, const char *user,
const char *repo_id, const char *token,
SyncTask *task)
{
HttpFolderPermReq *req;
GList *requests = NULL;
req = g_new0 (HttpFolderPermReq, 1);
memcpy (req->repo_id, repo_id, 36);
req->token = g_strdup(token);
req->timestamp = 0;
requests = g_list_append (requests, req);
server_state->checking_folder_perms = TRUE;
FolderPermInfo *info = g_new0 (FolderPermInfo, 1);
info->server_state = server_state;
info->task = task;
info->server = g_strdup (server);
info->user = g_strdup (user);
/* The requests list will be freed in http tx manager. */
if (http_tx_manager_get_folder_perms (seaf->http_tx_mgr,
server_state->effective_host,
server_state->use_fileserver_port,
requests,
check_repo_folder_perms_done,
info) < 0) {
seaf_warning ("Failed to schedule check repo %s folder permissions\n", repo_id);
server_state->checking_folder_perms = FALSE;
folder_perm_info_free (info);
seaf_sync_manager_set_task_error (task, SYNC_ERROR_FETCH);
}
}
static void
get_repo_sync_token_cb (HttpAPIGetResult *result, void *user_data)
{
SyncTask *task = user_data;
SyncInfo *sync_info = task->info;
HttpServerState *state = task->server_state;
json_t *object, *member;
json_error_t jerror;
const char *token;
int repo_version;
if (!result->success) {
task->tx_error_code = result->error_code;
if (task->tx_error_code == HTTP_TASK_ERR_FORBIDDEN)
seaf_sync_manager_set_task_error (task, SYNC_ERROR_ACCESS_DENIED);
else
seaf_sync_manager_set_task_error (task, SYNC_ERROR_FETCH);
return;
}
object = json_loadb (result->rsp_content, result->rsp_size, 0, &jerror);
if (!object) {
seaf_warning ("Parse response failed: %s.\n", jerror.text);
seaf_sync_manager_set_task_error (task, SYNC_ERROR_FETCH);
return;
}
member = json_object_get (object, "token");
if (!member) {
seaf_warning ("Invalid download info response: no token.\n");
seaf_sync_manager_set_task_error (task, SYNC_ERROR_FETCH);
goto out;
}
token = json_string_value (member);
if (!token) {
seaf_warning ("Invalid download info response: no token.\n");
seaf_sync_manager_set_task_error (task, SYNC_ERROR_FETCH);
goto out;
}
member = json_object_get (object, "repo_version");
if (!member) {
seaf_warning ("Invalid download info response: no repo_version.\n");
seaf_sync_manager_set_task_error (task, SYNC_ERROR_FETCH);
goto out;
}
repo_version = json_integer_value (member);
task->token = g_strdup (token);
// save repo token
RepoToken *repo_token = g_new0 (RepoToken, 1);
memcpy (repo_token->token, token, 40);
repo_token->token[40] = '\0';
repo_token->repo_version = repo_version;
g_hash_table_insert (seaf->sync_mgr->priv->repo_tokens, g_strdup (sync_info->repo_id), repo_token);
if (seaf_repo_manager_account_is_pro (seaf->repo_mgr, task->server, task->user)) {
int timestamp = seaf_repo_manager_get_folder_perm_timestamp (seaf->repo_mgr,
sync_info->repo_id);
if (timestamp <= 0) {
check_folder_permissions_by_repo (state, task->server, task->user, sync_info->repo_id, token, task);
goto out;
}
}
int rc = http_tx_manager_add_download (seaf->http_tx_mgr,
sync_info->repo_id,
repo_version,
task->server,
task->user,
state->effective_host,
token,
sync_info->repo_info->head_commit_id,
TRUE,
state->http_version,
state->use_fileserver_port,
NULL);
if (rc < 0) {
seaf_warning ("Failed to add download task for repo %s from %s.\n",
sync_info->repo_id, state->effective_host);
seaf_sync_manager_set_task_error (task, SYNC_ERROR_FETCH);
goto out;
}
transition_sync_state (task, SYNC_STATE_FETCH);
out:
json_decref (object);
}
static void
get_repo_sync_token (SeafSyncManager *manager,
SeafAccount *account,
HttpServerState *state,
SyncInfo *sync_info)
{
SyncTask *task;
task = sync_task_new (sync_info, state, account->server, account->username, NULL, TRUE);
RepoToken *repo_token = g_hash_table_lookup (seaf->sync_mgr->priv->repo_tokens,
sync_info->repo_id);
if(repo_token == NULL) {
char *url = g_strdup_printf ("%s/api2/repos/%s/download-info/",
account->server, sync_info->repo_id);
if (http_tx_manager_api_get (seaf->http_tx_mgr,
account->server,
url,
account->token,
get_repo_sync_token_cb,
task) < 0) {
seaf_warning ("Failed to start get repo sync token for %s from server %s\n",
sync_info->repo_id, account->server);
seaf_sync_manager_set_task_error (task, SYNC_ERROR_FETCH);
goto out;
}
transition_sync_state (task, SYNC_STATE_GET_TOKEN);
out:
g_free (url);
} else {
task->token = g_strdup(repo_token->token);
// In order to use folder perms when cloning repo, we nned to get folder perms once immediately after getting rep token.
if (account->is_pro) {
int timestamp = seaf_repo_manager_get_folder_perm_timestamp (seaf->repo_mgr,
sync_info->repo_id);
if (timestamp <= 0) {
check_folder_permissions_by_repo (state, account->server, account->username, sync_info->repo_id, repo_token->token, task);
return;
}
}
int rc = http_tx_manager_add_download (seaf->http_tx_mgr,
sync_info->repo_id,
repo_token->repo_version,
account->server,
account->username,
state->effective_host,
repo_token->token,
sync_info->repo_info->head_commit_id,
TRUE,
state->http_version,
state->use_fileserver_port,
NULL);
if (rc < 0) {
seaf_warning ("Failed to add download task for repo %s from %s.\n",
sync_info->repo_id, state->effective_host);
seaf_sync_manager_set_task_error (task, SYNC_ERROR_FETCH);
return;
}
transition_sync_state (task, SYNC_STATE_FETCH);
}
}
static void
clone_repo (SeafSyncManager *manager,
SeafAccount *account,
HttpServerState *state,
SyncInfo *sync_info)
{
get_repo_sync_token (manager, account, state, sync_info);
}
static gboolean
can_schedule_repo (SyncInfo *info)
{
gint64 now = (gint64)time(NULL);
return (info->last_sync_time == 0 ||
info->last_sync_time < now - DEFAULT_SYNC_INTERVAL);
}
static gboolean
create_commit_from_journal (SyncInfo *info, HttpServerState *state, SeafRepo *repo);
static int
check_head_commit_http (SyncTask *task);
inline static char *
get_basename (char *path)
{
char *slash;
slash = strrchr (path, '/');
if (!slash)
return path;
return (slash + 1);
}
static char *
exceed_max_deleted_files (SeafRepo *repo)
{
SeafBranch *master = NULL, *local = NULL;
SeafCommit *local_head = NULL, *master_head = NULL;
GList *diff_results = NULL;
char *deleted_file = NULL;
GString *desc = NULL;
char *ret = NULL;
local = seaf_branch_manager_get_branch (seaf->branch_mgr, repo->id, "local");
if (!local) {
seaf_warning ("No local branch found for repo %s(%.8s).\n",
repo->name, repo->id);
goto out;
}
master = seaf_branch_manager_get_branch (seaf->branch_mgr, repo->id, "master");
if (!master) {
seaf_warning ("No master branch found for repo %s(%.8s).\n",
repo->name, repo->id);
goto out;
}
local_head = seaf_commit_manager_get_commit (seaf->commit_mgr, repo->id, repo->version,
local->commit_id);
if (!local_head) {
seaf_warning ("Failed to get head of local branch for repo %s.\n", repo->id);
goto out;
}
master_head = seaf_commit_manager_get_commit (seaf->commit_mgr, repo->id, repo->version,
master->commit_id);
if (!master_head) {
seaf_warning ("Failed to get head of master branch for repo %s.\n", repo->id);
goto out;
}
diff_commit_roots (repo->id, repo->version, master_head->root_id, local_head->root_id, &diff_results, TRUE);
if (!diff_results) {
goto out;
}
GList *p;
DiffEntry *de;
int n_deleted = 0;
for (p = diff_results; p != NULL; p = p->next) {
de = p->data;
switch (de->status) {
case DIFF_STATUS_DELETED:
if (n_deleted == 0)
deleted_file = get_basename(de->name);
n_deleted++;
break;
}
}
if (n_deleted >= seaf->delete_confirm_threshold) {
desc = g_string_new ("");
g_string_append_printf (desc, "Deleted \"%s\" and %d more files.\n",
deleted_file, n_deleted - 1);
ret = g_string_free (desc, FALSE);
}
out:
seaf_branch_unref (local);
seaf_branch_unref (master);
seaf_commit_unref (local_head);
seaf_commit_unref (master_head);
g_list_free_full (diff_results, (GDestroyNotify)diff_entry_free);
return ret;
}
static void
notify_delete_confirmation (const char *repo_name, const char *desc, const char *confirmation_id)
{
json_t *msg = json_object ();
json_object_set_string_member (msg, "type", "del_confirmation");
json_object_set_string_member (msg, "repo_name", repo_name);
json_object_set_string_member (msg, "delete_files", desc);
json_object_set_string_member (msg, "confirmation_id", confirmation_id);
mq_mgr_push_msg (seaf->mq_mgr, SEADRIVE_NOTIFY_CHAN, msg);
}
int
seaf_sync_manager_add_del_confirmation (SeafSyncManager *mgr,
const char *confirmation_id,
gboolean resync)
{
SeafSyncManagerPriv *priv = seaf->sync_mgr->priv;
DelConfirmationResult *result = NULL;
result = g_new0 (DelConfirmationResult, 1);
result->resync = resync;
pthread_mutex_lock (&priv->del_confirmation_lock);
g_hash_table_insert (priv->del_confirmation_tasks, g_strdup (confirmation_id), result);
pthread_mutex_unlock (&priv->del_confirmation_lock);
return 0;
}
static DelConfirmationResult *
get_del_confirmation_result (const char *confirmation_id)
{
SeafSyncManagerPriv *priv = seaf->sync_mgr->priv;
DelConfirmationResult *result, *copy = NULL;
pthread_mutex_lock (&priv->del_confirmation_lock);
result = g_hash_table_lookup (priv->del_confirmation_tasks, confirmation_id);
if (result) {
copy = g_new0 (DelConfirmationResult, 1);
copy->resync = result->resync;
g_hash_table_remove (priv->del_confirmation_tasks, confirmation_id);
}
pthread_mutex_unlock (&priv->del_confirmation_lock);
return copy;
}
static int
sync_repo (SeafSyncManager *manager,
HttpServerState *state,
SyncInfo *sync_info,
SeafRepo *repo)
{
SeafBranch *master = NULL, *local = NULL;
SyncTask *task;
int ret = 0;
master = seaf_branch_manager_get_branch (seaf->branch_mgr, repo->id, "master");
if (!master) {
seaf_warning ("No master branch found for repo %s(%.8s).\n",
repo->name, repo->id);
ret = -1;
goto out;
}
local = seaf_branch_manager_get_branch (seaf->branch_mgr, repo->id, "local");
if (!local) {
seaf_warning ("No local branch found for repo %s(%.8s).\n",
repo->name, repo->id);
ret = -1;
goto out;
}
if (strcmp (local->commit_id, master->commit_id) != 0) {
if (can_schedule_repo (sync_info) || repo->force_sync_pending || sync_info->del_confirmation_pending) {
if (repo->force_sync_pending)
repo->force_sync_pending = FALSE;
task = sync_task_new (sync_info, state, NULL, NULL, repo->token, FALSE);
if (!sync_info->del_confirmation_pending) {
char *desc = NULL;
desc = exceed_max_deleted_files (repo);
if (desc) {
notify_delete_confirmation (repo->name, desc, local->commit_id);
seaf_warning ("Delete more than %d files, add delete confirmation.\n", seaf->delete_confirm_threshold);
sync_info->del_confirmation_pending = TRUE;
seaf_sync_manager_set_task_error (task, SYNC_ERROR_DEL_CONFIRMATION_PENDING);
g_free (desc);
goto out;
}
} else {
DelConfirmationResult *result = get_del_confirmation_result (local->commit_id);
if (!result) {
// User has not confirmed whether to continue syncing.
seaf_sync_manager_set_task_error (task, SYNC_ERROR_DEL_CONFIRMATION_PENDING);
goto out;
} else if (result->resync) {
// User chooses to resync.
g_free (result);
seaf_sync_manager_set_task_error (task, SYNC_ERROR_DEL_CONFIRMATION_PENDING);
// Delete this repo. It'll be re-synced when checking repo list next time.
seaf_repo_manager_mark_repo_deleted (seaf->repo_mgr, repo, FALSE);
goto out;
}
// User chooes to continue syncing.
g_free (result);
sync_info->del_confirmation_pending = FALSE;
}
int rc = http_tx_manager_add_upload (seaf->http_tx_mgr,
repo->id,
repo->version,
repo->server,
repo->user,
repo->repo_uname,
state->effective_host,
repo->token,
state->http_version,
state->use_fileserver_port,
NULL);
if (rc < 0) {
seaf_warning ("Failed to add upload task for repo %s to %s.\n",
sync_info->repo_id, state->effective_host);
seaf_sync_manager_set_task_error (task, SYNC_ERROR_START_UPLOAD);
ret = -1;
goto out;
}
transition_sync_state (task, SYNC_STATE_UPLOAD);
}
goto out;
}
if (create_commit_from_journal (sync_info, state, repo)) {
if (repo->force_sync_pending)
repo->force_sync_pending = FALSE;
goto out;
}
if ((strcmp (sync_info->repo_info->head_commit_id, master->commit_id) != 0 &&
can_schedule_repo (sync_info)) ||
repo->force_sync_pending) {
if (repo->force_sync_pending)
repo->force_sync_pending = FALSE;
task = sync_task_new (sync_info, state, NULL, NULL, repo->token, FALSE);
/* In some cases, sync_info->repo_info->head_commit_id may be outdated.
* To avoid mistakenly download a repo, we check server head commit
* before starting download.
*/
check_head_commit_http (task);
}
out:
seaf_branch_unref (local);
seaf_branch_unref (master);
return ret;
}
static void
check_head_commit_done (HttpHeadCommit *result, void *user_data)
{
SyncTask *task = user_data;
SyncInfo *info = task->info;
SeafRepo *repo = task->repo;
HttpServerState *state = task->server_state;
SeafBranch *master = NULL;
if (!result->check_success) {
task->tx_error_code = result->error_code;
if (result->perm_denied)
seaf_sync_manager_set_task_error (task, SYNC_ERROR_ACCESS_DENIED);
else
seaf_sync_manager_set_task_error (task, SYNC_ERROR_GET_SYNC_INFO);
/* if (result->perm_denied) */
/* seaf_repo_manager_mark_repo_deleted (seaf->repo_mgr, repo, FALSE); */
return;
}
if (result->is_deleted) {
seaf_sync_manager_set_task_error (task, SYNC_ERROR_NOREPO);
return;
}
if (result->is_corrupt) {
seaf_sync_manager_set_task_error (task, SYNC_ERROR_REPO_CORRUPT);
return;
}
/* The cached head commit id may be outdated. */
if (strcmp (result->head_commit, info->repo_info->head_commit_id) != 0) {
seaf_repo_manager_set_repo_info_head_commit (seaf->repo_mgr,
repo->id,
result->head_commit);
}
master = seaf_branch_manager_get_branch (seaf->branch_mgr, repo->id, "master");
if (!master) {
seaf_warning ("master branch not found for repo %s.\n", repo->id);
seaf_sync_manager_set_task_error (task, SYNC_ERROR_DATA_CORRUPT);
return;
}
if (strcmp (result->head_commit, master->commit_id) != 0) {
int rc = http_tx_manager_add_download (seaf->http_tx_mgr,
repo->id,
repo->version,
repo->server,
repo->user,
state->effective_host,
repo->token,
result->head_commit,
FALSE,
state->http_version,
state->use_fileserver_port,
NULL);
if (rc < 0) {
seaf_warning ("Failed to add download task for repo %s from %s.\n",
info->repo_id, state->effective_host);
seaf_sync_manager_set_task_error (task, SYNC_ERROR_START_FETCH);
goto out;
}
transition_sync_state (task, SYNC_STATE_FETCH);
} else {
transition_sync_state (task, SYNC_STATE_DONE);
}
out:
seaf_branch_unref (master);
}
static int
check_head_commit_http (SyncTask *task)
{
SeafRepo *repo = task->repo;
HttpServerState *state = task->server_state;
int ret = http_tx_manager_check_head_commit (seaf->http_tx_mgr,
repo->id, repo->version,
state->effective_host,
repo->token,
state->use_fileserver_port,
check_head_commit_done,
task);
if (ret == 0)
transition_sync_state (task, SYNC_STATE_INIT);
else if (ret < 0)
seaf_sync_manager_set_task_error (task, SYNC_ERROR_GET_SYNC_INFO);
return ret;
}
static gboolean
server_is_pro (const char *server_url) {
GList *accounts = NULL, *ptr;
SeafAccount *account;
gboolean is_pro = FALSE;
accounts = seaf_repo_manager_get_account_list (seaf->repo_mgr);
if (!accounts)
return FALSE;
for (ptr = accounts; ptr; ptr = ptr->next) {
account = ptr->data;
if (g_strcmp0 (account->fileserver_addr, server_url) == 0) {
is_pro = account->is_pro;
break;
}
}
g_list_free_full (accounts, (GDestroyNotify)seaf_account_free);
return is_pro;
}
void
seaf_sync_manager_check_locks_and_folder_perms (SeafSyncManager *manager, const char *server_url)
{
HttpServerState *state;
state = get_http_server_state (manager->priv, server_url);
if (!state) {
return;
}
if (server_is_pro (server_url)) {
state->immediate_check_folder_perms = TRUE;
state->immediate_check_locked_files = TRUE;
}
return;
}
gboolean
seaf_sync_manager_ignored_on_checkout (const char *file_path, IgnoreReason *ignore_reason)
{
gboolean ret = FALSE;
return ret;
}
gboolean
seaf_repo_manager_ignored_on_commit (const char *filename)
{
GPatternSpec **spec = ignore_patterns;
if (!g_utf8_validate (filename, -1, NULL)) {
seaf_warning ("File name %s contains non-UTF8 characters, skip.\n", filename);
return TRUE;
}
/* Ignore file/dir if its name is too long. */
if (strlen(filename) >= SEAF_DIR_NAME_LEN)
return TRUE;
if (strchr (filename, '/'))
return TRUE;
while (*spec) {
if (g_pattern_match_string(*spec, filename))
return TRUE;
spec++;
}
if (!seaf->sync_extra_temp_file) {
spec = office_temp_ignore_patterns;
while (*spec) {
if (g_pattern_match_string(*spec, filename))
return TRUE;
spec++;
}
}
return FALSE;
}
#if 0
static int
copy_file_to_conflict_file (const char *repo_id,
const char *path,
const char *conflict_path)
{
SeafRepo *repo = NULL;
RepoTreeStat st;
JournalOp *op;
int ret = 0;
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo) {
ret = -1;
goto out;
}
if (repo_tree_stat_path (repo->tree, path, &st) < 0) {
ret = -1;
goto out;
}
repo_tree_create_file (repo->tree, conflict_path, st.id, st.mode, st.mtime, st.size);
op = journal_op_new (OP_TYPE_CREATE_FILE, conflict_path, NULL, st.size, st.mtime, st.mode);
if (journal_append_op (repo->journal, op) < 0) {
journal_op_free (op);
ret = -1;
goto out;
}
file_cache_mgr_rename (seaf->file_cache_mgr, repo_id, path, repo_id, conflict_path);
out:
seaf_repo_unref (repo);
return ret;
}
#endif
static int
merge_remote_head_to_repo_tree (SeafRepo *repo, const char *new_head_id)
{
SeafBranch *master = NULL;
SeafCommit *local_head = NULL, *remote_head = NULL;
GList *diff_results = NULL, *ptr;
DiffEntry *de;
char id[41];
/* gboolean conflicted; */
int ret = 0;
master = seaf_branch_manager_get_branch (seaf->branch_mgr,
repo->id,
"master");
if (!master) {
seaf_warning ("Failed to get master branch of repo %s.\n", repo->id);
ret = -1;
goto out;
}
local_head = seaf_commit_manager_get_commit (seaf->commit_mgr,
repo->id, repo->version,
repo->head->commit_id);
if (!local_head) {
seaf_warning ("Failed to get head commit of local branch for repo %s.\n",
repo->id);
ret = -1;
goto out;
}
remote_head = seaf_commit_manager_get_commit (seaf->commit_mgr,
repo->id, repo->version,
new_head_id);
if (!remote_head) {
seaf_warning ("Failed to get remote head commit %s for repo %s.\n",
new_head_id, repo->id);
ret = -1;
goto out;
}
if (diff_commits (local_head, remote_head, &diff_results, TRUE) < 0) {
seaf_warning ("Failed to diff for repo %s.\n", repo->id);
ret = -1;
goto out;
}
/* Process delete/rename before add/update. Since rename of empty files/dirs
* are interpreted as delete+create, we need to be sure about the order.
*/
for (ptr = diff_results; ptr; ptr = ptr->next) {
de = (DiffEntry *)ptr->data;
switch (de->status) {
case DIFF_STATUS_DELETED:
/* If local file was changed, don't delete it. */
if (file_cache_mgr_is_file_changed (seaf->file_cache_mgr,
repo->id, de->name, TRUE))
break;
repo_tree_unlink (repo->tree, de->name);
file_cache_mgr_unlink (seaf->file_cache_mgr, repo->id, de->name);
break;
case DIFF_STATUS_DIR_DELETED:
repo_tree_remove_subtree (repo->tree, de->name);
break;
case DIFF_STATUS_RENAMED:
case DIFF_STATUS_DIR_RENAMED:
if (seaf_sync_manager_ignored_on_checkout(de->new_name, NULL)) {
seaf_message ("Path %s is invalid on Windows, skip rename.\n",
de->new_name);
/* send_sync_error_notification (repo->id, repo->name, de->new_name, */
/* SYNC_ERROR_ID_INVALID_PATH); */
break;
} else if (seaf_sync_manager_ignored_on_checkout(de->name, NULL)) {
/* If the server renames an invalid path to a valid path,
* directly checkout the valid path.
*/
rawdata_to_hex (de->sha1, id, 20);
repo_tree_add_subtree (repo->tree,
de->new_name,
id,
de->mtime);
break;
}
repo_tree_rename (repo->tree, de->name, de->new_name, TRUE);
file_cache_mgr_rename (seaf->file_cache_mgr,
repo->id, de->name,
repo->id, de->new_name);
break;
}
}
for (ptr = diff_results; ptr; ptr = ptr->next) {
de = (DiffEntry *)ptr->data;
switch (de->status) {
case DIFF_STATUS_ADDED:
if (seaf_sync_manager_ignored_on_checkout(de->name, NULL)) {
seaf_message ("Path %s is invalid on Windows, skip creating.\n",
de->name);
/* send_sync_error_notification (repo->id, repo->name, de->name, */
/* SYNC_ERROR_ID_INVALID_PATH); */
break;
}
/* handle_file_conflict (repo->id, de->name, &conflicted); */
rawdata_to_hex (de->sha1, id, 20);
/* if (conflicted) { */
/* repo_tree_set_file_mtime (repo->tree, */
/* de->name, */
/* de->mtime); */
/* repo_tree_set_file_size (repo->tree, */
/* de->name, */
/* de->size); */
/* repo_tree_set_file_id (repo->tree, de->name, id); */
/* } else { */
repo_tree_create_file (repo->tree,
de->name,
id,
de->mode,
de->mtime,
de->size);
/* } */
break;
case DIFF_STATUS_DIR_ADDED:
if (seaf_sync_manager_ignored_on_checkout(de->name, NULL)) {
seaf_message ("Path %s is invalid on Windows, skip creating.\n",
de->name);
/* send_sync_error_notification (repo->id, repo->name, de->name, */
/* SYNC_ERROR_ID_INVALID_PATH); */
break;
}
rawdata_to_hex (de->sha1, id, 20);
repo_tree_add_subtree (repo->tree,
de->name,
id,
de->mtime);
break;
case DIFF_STATUS_MODIFIED:
if (seaf_sync_manager_ignored_on_checkout(de->name, NULL)) {
seaf_message ("Path %s is invalid on Windows, skip update.\n",
de->name);
/* send_sync_error_notification (repo->id, repo->name, de->name, */
/* SYNC_ERROR_ID_INVALID_PATH); */
break;
}
/* handle_file_conflict (repo->id, de->name, &conflicted); */
rawdata_to_hex (de->sha1, id, 20);
repo_tree_set_file_mtime (repo->tree,
de->name,
de->mtime);
repo_tree_set_file_size (repo->tree,
de->name,
de->size);
repo_tree_set_file_id (repo->tree, de->name, id);
break;
}
}
seaf_branch_set_commit (master, new_head_id);
master->opid = repo->head->opid;
seaf_branch_set_commit (repo->head, new_head_id);
/* Update both branches in db in a single operatoin, to prevent race conditions. */
seaf_branch_manager_update_repo_branches (seaf->branch_mgr, repo->head);
/* Update repo name if it's changed on server. */
if (g_strcmp0 (local_head->repo_name, remote_head->repo_name) != 0) {
seaf_repo_manager_rename_repo (seaf->repo_mgr, repo->id, remote_head->repo_name);
}
out:
seaf_branch_unref (master);
seaf_commit_unref (local_head);
seaf_commit_unref (remote_head);
g_list_free_full (diff_results, (GDestroyNotify)diff_entry_free);
return ret;
}
typedef struct _LoadRepoTreeAux {
SyncTask *task;
char new_head_id[41];
gboolean success;
} LoadRepoTreeAux;
static void *
load_repo_tree_job (void *vdata)
{
LoadRepoTreeAux *aux = vdata;
SyncTask *task = aux->task;
SeafRepo *repo = task->repo;
if (task->is_clone) {
if (seaf_repo_load_fs (repo, TRUE) < 0) {
aux->success = FALSE;
} else {
aux->success = TRUE;
}
} else {
if (merge_remote_head_to_repo_tree (repo, aux->new_head_id) < 0) {
aux->success = FALSE;
} else {
aux->success = TRUE;
}
}
return aux;
}
static void
load_repo_tree_done (void *vresult)
{
LoadRepoTreeAux *aux = vresult;
SyncTask *task = aux->task;
SeafSyncManagerPriv *priv = seaf->sync_mgr->priv;
if (aux->success) {
g_hash_table_remove (priv->repo_tokens, task->repo->id);
transition_sync_state (task, SYNC_STATE_DONE);
} else {
seaf_sync_manager_set_task_error (task, SYNC_ERROR_DATA_CORRUPT);
}
g_free (aux);
}
static void
handle_repo_fetched_clone (SeafileSession *seaf,
HttpTxTask *tx_task,
SeafSyncManager *mgr)
{
SyncInfo *info = get_sync_info (mgr, tx_task->repo_id);
SyncTask *task = info->current_task;
if (!task) {
seaf_warning ("BUG: sync task not found after fetch is done.\n");
return;
}
if (tx_task->state == HTTP_TASK_STATE_CANCELED) {
transition_sync_state (task, SYNC_STATE_CANCELED);
return;
} else if (tx_task->state == HTTP_TASK_STATE_ERROR) {
task->tx_error_code = tx_task->error;
if (tx_task->error == HTTP_TASK_ERR_FORBIDDEN) {
seaf_sync_manager_set_task_error (task, SYNC_ERROR_ACCESS_DENIED);
} else if (tx_task->error == HTTP_TASK_ERR_FILE_LOCKED) {
task->unsyncable_path = tx_task->unsyncable_path;
seaf_sync_manager_set_task_error (task, SYNC_ERROR_FILES_LOCKED_BY_USER);
}
else if (tx_task->error == HTTP_TASK_ERR_FOLDER_PERM_DENIED) {
task->unsyncable_path = tx_task->unsyncable_path;
seaf_sync_manager_set_task_error (task, SYNC_ERROR_FOLDER_PERM_DENIED);
} else if (tx_task->error == HTTP_TASK_ERR_NO_PERMISSION_TO_SYNC) {
task->unsyncable_path = tx_task->unsyncable_path;
seaf_sync_manager_set_task_error (task, SYNC_ERROR_PERM_NOT_SYNCABLE);
} else if (tx_task->error == HTTP_TASK_ERR_NO_WRITE_PERMISSION) {
seaf_sync_manager_set_task_error (task, SYNC_ERROR_NO_WRITE_PERMISSION);
} else
seaf_sync_manager_set_task_error (task, SYNC_ERROR_FETCH);
return;
}
SeafRepo *repo = seaf_repo_manager_get_repo (seaf->repo_mgr,
tx_task->repo_id);
if (repo == NULL) {
seaf_warning ("Cannot find repo %s after fetched.\n",
tx_task->repo_id);
seaf_sync_manager_set_task_error (task, SYNC_ERROR_DATA_CORRUPT);
return;
}
seaf_repo_manager_set_repo_token (seaf->repo_mgr, repo, task->token);
if (info->repo_info->is_readonly)
seaf_repo_set_readonly (repo);
if (!repo->worktree)
seaf_repo_set_worktree (repo, info->repo_info->display_name);
/* Set task->repo since the repo exists now. */
task->repo = repo;
LoadRepoTreeAux *aux = g_new(LoadRepoTreeAux, 1);
aux->task = task;
if (seaf_job_manager_schedule_job (seaf->job_mgr,
load_repo_tree_job,
load_repo_tree_done,
aux) < 0) {
seaf_warning ("Failed to start load repo tree job.\n");
seaf_sync_manager_set_task_error (task, SYNC_ERROR_UNKNOWN);
g_free (aux);
return;
}
transition_sync_state (task, SYNC_STATE_LOAD_REPO);
}
static void
handle_repo_fetched_sync (SeafileSession *seaf,
HttpTxTask *tx_task,
SeafSyncManager *mgr)
{
SyncInfo *info = get_sync_info (mgr, tx_task->repo_id);
SyncTask *task = info->current_task;
SeafRepo *repo;
if (!task) {
seaf_warning ("BUG: sync task not found after fetch is done.\n");
return;
}
repo = task->repo;
if (repo->delete_pending) {
transition_sync_state (task, SYNC_STATE_CANCELED);
return;
}
if (tx_task->state == HTTP_TASK_STATE_CANCELED) {
transition_sync_state (task, SYNC_STATE_CANCELED);
return;
} else if (tx_task->state == HTTP_TASK_STATE_ERROR) {
task->tx_error_code = tx_task->error;
if (tx_task->error == HTTP_TASK_ERR_FORBIDDEN) {
seaf_sync_manager_set_task_error (task, SYNC_ERROR_ACCESS_DENIED);
} else if (tx_task->error == HTTP_TASK_ERR_FILE_LOCKED) {
seaf_sync_manager_set_task_error (task, SYNC_ERROR_FILES_LOCKED_BY_USER);
} else if (tx_task->error == HTTP_TASK_ERR_FOLDER_PERM_DENIED) {
seaf_sync_manager_set_task_error (task, SYNC_ERROR_FOLDER_PERM_DENIED);
} else if (tx_task->error == HTTP_TASK_ERR_NO_PERMISSION_TO_SYNC) {
task->unsyncable_path = tx_task->unsyncable_path;
seaf_sync_manager_set_task_error (task, SYNC_ERROR_PERM_NOT_SYNCABLE);
} else if (tx_task->error == HTTP_TASK_ERR_NO_WRITE_PERMISSION) {
seaf_sync_manager_set_task_error (task, SYNC_ERROR_NO_WRITE_PERMISSION);
} else
seaf_sync_manager_set_task_error (task, SYNC_ERROR_FETCH);
return;
}
LoadRepoTreeAux *aux = g_new0 (LoadRepoTreeAux, 1);
aux->task = task;
memcpy (aux->new_head_id, tx_task->head, 40);
if (seaf_job_manager_schedule_job (seaf->job_mgr,
load_repo_tree_job,
load_repo_tree_done,
aux) < 0) {
seaf_warning ("Failed to start load repo tree job.\n");
seaf_sync_manager_set_task_error (task, SYNC_ERROR_UNKNOWN);
g_free (aux);
return;
}
transition_sync_state (task, SYNC_STATE_LOAD_REPO);
}
static void
on_repo_http_fetched (SeafileSession *seaf,
HttpTxTask *tx_task,
SeafSyncManager *mgr)
{
if (tx_task->is_clone)
handle_repo_fetched_clone (seaf, tx_task, mgr);
else
handle_repo_fetched_sync (seaf, tx_task, mgr);
}
static void
print_upload_corrupt_debug_info (SeafRepo *repo)
{
SeafBranch *local = NULL, *master = NULL;
SeafCommit *local_head = NULL, *master_head = NULL;
seaf_message ("Repo %s(%s) local metadata is found corrupted when upload. "
"Printing debug information and removing the local repo. "
"It will be resynced later.\n", repo->name, repo->id);
local = seaf_branch_manager_get_branch (seaf->branch_mgr, repo->id, "local");
if (!local) {
seaf_warning ("Failed to get local branch of repo %s.\n", repo->id);
goto out;
}
master = seaf_branch_manager_get_branch (seaf->branch_mgr, repo->id, "master");
if (!master) {
seaf_warning ("Failed to get master branch of repo %s.\n", repo->id);
goto out;
}
GList *ops = NULL, *ptr;
JournalOp *op;
gboolean error = FALSE;
seaf_message ("Operations in journal used to create the commit "
"(op id %"G_GINT64_FORMAT" to %"G_GINT64_FORMAT"):\n",
master->opid + 1, local->opid);
ops = journal_read_ops (repo->journal, master->opid + 1, local->opid, &error);
for (ptr = ops; ptr; ptr = ptr->next) {
op = ptr->data;
seaf_message ("%"G_GINT64_FORMAT", %d, %s, %s, %"
G_GINT64_FORMAT", %"G_GINT64_FORMAT", %u\n",
op->opid, op->type, op->path, op->new_path, op->size, op->mtime, op->mode);
}
g_list_free_full (ops, (GDestroyNotify)journal_op_free);
local_head = seaf_commit_manager_get_commit (seaf->commit_mgr, repo->id, repo->version,
local->commit_id);
if (!local_head) {
seaf_warning ("Failed to get head of local branch for repo %s.\n", repo->id);
goto out;
}
master_head = seaf_commit_manager_get_commit (seaf->commit_mgr, repo->id, repo->version,
master->commit_id);
if (!master_head) {
seaf_warning ("Failed to get head of master branch for repo %s.\n", repo->id);
goto out;
}
GList *results = NULL;
DiffEntry *de;
if (diff_commits (master_head, local_head, &results, TRUE) < 0) {
seaf_warning ("Failed to diff repo %s.\n", repo->id);
goto out;
}
seaf_message ("Diff results:\n");
for (ptr = results; ptr; ptr = ptr->next) {
de = ptr->data;
seaf_message ("%c %s %s\n", de->status, de->name, de->new_name);
}
g_list_free_full (results, (GDestroyNotify)diff_entry_free);
out:
seaf_branch_unref (local);
seaf_branch_unref (master);
seaf_commit_unref (local_head);
seaf_commit_unref (master_head);
return;
}
static void
on_repo_http_uploaded (SeafileSession *seaf,
HttpTxTask *tx_task,
SeafSyncManager *manager)
{
SyncInfo *info = get_sync_info (manager, tx_task->repo_id);
SyncTask *task = info->current_task;
if (task->repo->delete_pending) {
transition_sync_state (task, SYNC_STATE_CANCELED);
return;
}
if (tx_task->state == HTTP_TASK_STATE_FINISHED) {
seaf_message ("removing blocks for repo %s\n", tx_task->repo_id);
/* Since the sync loop is a background thread, it should be no problem
* to block it a bit.
*/
seaf_block_manager_remove_store (seaf->block_mgr, task->repo->id);
task->uploaded = TRUE;
check_head_commit_http (task);
} else if (tx_task->state == HTTP_TASK_STATE_CANCELED) {
transition_sync_state (task, SYNC_STATE_CANCELED);
} else if (tx_task->state == HTTP_TASK_STATE_ERROR) {
task->tx_error_code = tx_task->error;
if (tx_task->error == HTTP_TASK_ERR_FORBIDDEN) {
seaf_sync_manager_set_task_error (task, SYNC_ERROR_ACCESS_DENIED);
} else if (tx_task->error == HTTP_TASK_ERR_FILE_LOCKED) {
task->unsyncable_path = tx_task->unsyncable_path;
seaf_sync_manager_set_task_error (task, SYNC_ERROR_FILES_LOCKED_BY_USER);
} else if (tx_task->error == HTTP_TASK_ERR_FOLDER_PERM_DENIED) {
task->unsyncable_path = tx_task->unsyncable_path;
seaf_sync_manager_set_task_error (task, SYNC_ERROR_FOLDER_PERM_DENIED);
} else if (tx_task->error == HTTP_TASK_ERR_TOO_MANY_FILES) {
seaf_sync_manager_set_task_error (task, SYNC_ERROR_TOO_MANY_FILES);
} else if (tx_task->error == HTTP_TASK_ERR_NO_WRITE_PERMISSION) {
seaf_sync_manager_set_task_error (task, SYNC_ERROR_NO_WRITE_PERMISSION);
} else if (tx_task->error == HTTP_TASK_ERR_NO_PERMISSION_TO_SYNC) {
task->unsyncable_path = tx_task->unsyncable_path;
seaf_sync_manager_set_task_error (task, SYNC_ERROR_PERM_NOT_SYNCABLE);
} else if (tx_task->error == HTTP_TASK_ERR_NO_QUOTA) {
seaf_sync_manager_set_task_error (task, SYNC_ERROR_QUOTA_FULL);
/* Only notify "quota full" once. */
/* if (!task->repo->quota_full_notified) { */
/* send_sync_error_notification (repo->id, repo->name, NULL, */
/* SYNC_ERROR_ID_QUOTA_FULL); */
/* task->repo->quota_full_notified = 1; */
/* } */
} else if (tx_task->error == HTTP_TASK_ERR_BLOCK_MISSING) {
seaf_sync_manager_set_task_error (task, SYNC_ERROR_BLOCK_MISSING);
} else {
if (tx_task->error == HTTP_TASK_ERR_BAD_LOCAL_DATA)
print_upload_corrupt_debug_info (task->repo);
seaf_sync_manager_set_task_error (task, SYNC_ERROR_UPLOAD);
}
}
}
#define MAX_COMMIT_SIZE 100 * (1 << 20) /* 100MB */
static gboolean
is_repo_writable (SeafRepo *repo)
{
if (repo->is_readonly && seaf_repo_manager_get_folder_perm_timestamp (seaf->repo_mgr, repo->id) < 0)
return FALSE;
return TRUE;
}
static void
handle_update_file_op (SeafRepo *repo, ChangeSet *changeset, const char *username,
JournalOp *op, gboolean renamed_from_ignored,
GHashTable *updated_files, gint64 *total_size,
SeafileCrypt *crypt)
{
unsigned char sha1[20] = {0};
SeafStat st;
FileCacheStat cache_st, *file_info;
gboolean changed = FALSE;
/* If index doesn't complete, st will be all-zero. */
memset (&st, 0, sizeof(st));
if (file_cache_mgr_index_file (seaf->file_cache_mgr,
repo->id,
repo->version,
op->path,
crypt,
TRUE,
sha1,
&changed) < 0) {
seaf_warning ("Failed to index file %s in repo %s, skip.\n",
op->path, repo->id);
goto out;
}
if (file_cache_mgr_stat (seaf->file_cache_mgr,
repo->id, op->path,
&cache_st) == 0) {
st.st_size = cache_st.size;
st.st_mtime = cache_st.mtime;
st.st_mode = op->mode;
} else {
st.st_size = op->size;
st.st_mtime = op->mtime;
st.st_mode = op->mode;
}
add_to_changeset (changeset,
DIFF_STATUS_MODIFIED,
sha1,
&st,
username,
op->path,
NULL);
if (changed)
*total_size += (gint64)st.st_size;
out:
file_info = g_new0 (FileCacheStat, 1);
rawdata_to_hex (sha1, file_info->file_id, 20);
file_info->mtime = st.st_mtime;
file_info->size = st.st_size;
/* Keep the last information if there was one in the hash table. */
g_hash_table_replace (updated_files, g_strdup(op->path), file_info);
}
typedef struct CheckRenameAux {
SeafRepo *repo;
ChangeSet *changeset;
const char *username;
GHashTable *updated_files;
gint64 *total_size;
gboolean renamed_from_ignored;
} CheckRenameAux;
static void
check_renamed_file_cb (const char *repo_id, const char *file_path,
SeafStat *st, void *user_data)
{
char *filename = g_path_get_basename (file_path);
gboolean file_ignored = seaf_repo_manager_ignored_on_commit (filename);
if (file_ignored)
return;
CheckRenameAux *aux = (CheckRenameAux *)user_data;
JournalOp *op;
SeafRepo *repo = NULL;
SeafileCrypt *crypt = NULL;
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo)
return;
if (repo->encrypted)
crypt = seafile_crypt_new (repo->enc_version, repo->enc_key, repo->enc_iv);
/* Create a faked journal op. */
op = journal_op_new (OP_TYPE_UPDATE_FILE, file_path, NULL,
(gint64)st->st_size, (gint64)st->st_mtime, (guint32)st->st_mode);
handle_update_file_op (aux->repo, aux->changeset, aux->username,
op, aux->renamed_from_ignored,
aux->updated_files, aux->total_size,
crypt);
g_free (crypt);
journal_op_free (op);
seaf_repo_unref (repo);
}
/* Excel first writes update to a temporary file and then rename the file to
* xlsx/xls. Unfortunately the temp file dosen't have specific pattern.
* We can only ignore renaming from non xlsx file to xlsx file.
*/
static gboolean
ignore_xlsx_update (const char *src_path, const char *dst_path)
{
GPatternSpec *pattern_xlsx = g_pattern_spec_new ("*.xlsx");
GPatternSpec *pattern_xls = g_pattern_spec_new ("*.xls");
int ret = FALSE;
if (!g_pattern_match_string(pattern_xlsx, src_path) &&
g_pattern_match_string(pattern_xlsx, dst_path))
ret = TRUE;
if (!g_pattern_match_string(pattern_xls, src_path) &&
g_pattern_match_string(pattern_xls, dst_path))
ret = TRUE;
g_pattern_spec_free (pattern_xlsx);
g_pattern_spec_free (pattern_xls);
return ret;
}
static void
handle_rename_op (SeafRepo *repo, ChangeSet *changeset, const char *username,
GHashTable *updated_files, JournalOp *op, gint64 *total_size)
{
char *src_filename, *dst_filename;
gboolean src_ignored, dst_ignored;
src_filename = g_path_get_basename (op->path);
dst_filename = g_path_get_basename (op->new_path);
src_ignored = (seaf_repo_manager_ignored_on_commit (src_filename) ||
ignore_xlsx_update (src_filename, dst_filename));
dst_ignored = seaf_repo_manager_ignored_on_commit (dst_filename);
/* If destination path is ignored, just remove the src path. */
if (dst_ignored) {
remove_from_changeset (changeset,
op->path,
FALSE,
NULL);
goto out;
}
/* Now destination is not ignored. */
if (!src_ignored) {
add_to_changeset (changeset,
DIFF_STATUS_RENAMED,
NULL,
NULL,
username,
op->path,
op->new_path);
}
/* We should always scan the destination to check whether files are
* changed. For example, in the following case:
* 1. file a.txt is updated;
* 2. a.txt is moved to test/a.txt;
* If the two operations are executed in a batch, the updated content
* of a.txt won't be committed if we don't scan the destination, because
* when we process the update operation, a.txt is already not in its
* original place.
*/
CheckRenameAux aux;
aux.repo = repo;
aux.changeset = changeset;
aux.username = username;
aux.updated_files = updated_files;
aux.total_size = total_size;
aux.renamed_from_ignored = src_ignored;
file_cache_mgr_traverse_path (seaf->file_cache_mgr,
repo->id,
op->new_path,
check_renamed_file_cb,
NULL,
&aux);
out:
g_free (src_filename);
g_free (dst_filename);
}
static int
apply_journal_ops_to_changeset (SeafRepo *repo, ChangeSet *changeset,
const char *username, gint64 *last_opid,
GHashTable *updated_files)
{
GList *ops, *ptr;
JournalOp *op, *next_op;
SeafStat st;
gboolean error;
unsigned char allzero[20] = {0};
gint64 total_size = 0;
char *filename;
RepoTreeStat tree_st;
SeafileCrypt *crypt = NULL;
ops = journal_read_ops (repo->journal, *last_opid + 1, G_MAXINT64, &error);
if (error) {
seaf_warning ("Failed to read operations from journal for repo %s.\n",
repo->id);
return -1;
}
if (!ops) {
seaf_message ("All operations of repo %s(%.8s) have been processed.\n",
repo->name, repo->id);
repo->partial_commit_mode = FALSE;
return 0;
}
if (repo->encrypted)
crypt = seafile_crypt_new (repo->enc_version, repo->enc_key, repo->enc_iv);
for (ptr = ops; ptr; ptr = ptr->next) {
op = (JournalOp *)ptr->data;
if (ptr->next)
next_op = (JournalOp *)(ptr->next->data);
else
next_op = NULL;
filename = g_path_get_basename (op->path);
seaf_debug ("Processing event %d %s\n", op->type, op->path);
switch (op->type) {
case OP_TYPE_CREATE_FILE:
/* If the next op is update the same file, skip this one. */
if (next_op &&
next_op->type == OP_TYPE_UPDATE_FILE &&
strcmp (next_op->path, op->path) == 0)
break;
if (seaf_repo_manager_ignored_on_commit (filename))
break;
st.st_size = op->size;
st.st_mtime = op->mtime;
st.st_mode = op->mode;
add_to_changeset (changeset,
DIFF_STATUS_ADDED,
allzero,
&st,
username,
op->path,
NULL);
break;
case OP_TYPE_DELETE_FILE:
if (seaf_repo_manager_ignored_on_commit (filename))
break;
remove_from_changeset (changeset,
op->path,
FALSE,
NULL);
g_hash_table_remove (updated_files, op->path);
break;
case OP_TYPE_UPDATE_FILE:
/* If the next op is update the same file, skip this one. */
if (next_op &&
next_op->type == OP_TYPE_UPDATE_FILE &&
strcmp (next_op->path, op->path) == 0)
break;
if (seaf_repo_manager_ignored_on_commit (filename))
break;
handle_update_file_op (repo, changeset, username, op, FALSE,
updated_files, &total_size, crypt);
if (total_size >= MAX_COMMIT_SIZE) {
seaf_message ("Creating partial commit after adding %s in repo %s(%.8s).\n",
op->path, repo->name, repo->id);
repo->partial_commit_mode = TRUE;
*last_opid = op->opid;
g_free (filename);
goto out;
}
break;
case OP_TYPE_RENAME:
handle_rename_op (repo, changeset, username,
updated_files, op, &total_size);
if (total_size >= MAX_COMMIT_SIZE) {
seaf_message ("Creating partial commit after rename %s in repo %s(%.8s).\n",
op->path, repo->name, repo->id);
repo->partial_commit_mode = TRUE;
*last_opid = op->opid;
g_free (filename);
goto out;
}
break;
case OP_TYPE_MKDIR:
if (seaf_repo_manager_ignored_on_commit (filename))
break;
st.st_size = op->size;
st.st_mtime = op->mtime;
st.st_mode = S_IFDIR;
add_to_changeset (changeset,
DIFF_STATUS_DIR_ADDED,
allzero,
&st,
username,
op->path,
NULL);
break;
case OP_TYPE_RMDIR:
if (seaf_repo_manager_ignored_on_commit (filename))
break;
remove_from_changeset (changeset,
op->path,
FALSE,
NULL);
break;
case OP_TYPE_UPDATE_ATTR:
if (seaf_repo_manager_ignored_on_commit (filename))
break;
/* Don't update the file if it doesn't exist in the current
* repo tree.
*/
if (repo_tree_stat_path (repo->tree, op->path, &tree_st) < 0) {
break;
}
st.st_size = op->size;
st.st_mtime = op->mtime;
st.st_mode = op->mode;
add_to_changeset (changeset,
DIFF_STATUS_MODIFIED,
NULL,
&st,
username,
op->path,
NULL);
break;
default:
seaf_warning ("Unknwon op type %d, skipped.\n", op->type);
}
g_free (filename);
if (!ptr->next) {
seaf_message ("All operations of repo %s(%.8s) have been processed.\n",
repo->name, repo->id);
repo->partial_commit_mode = FALSE;
*last_opid = op->opid;
}
}
out:
g_free (crypt);
g_list_free_full (ops, (GDestroyNotify)journal_op_free);
return 0;
}
static int
update_head_commit (SeafRepo *repo, const char *root_id,
const char *desc, const char *username,
gint64 last_opid)
{
SeafCommit *commit;
int ret = 0;
commit = seaf_commit_new (NULL, repo->id, root_id,
username,
seaf->client_id,
desc, 0);
commit->parent_id = g_strdup (repo->head->commit_id);
/* Add this computer's name to commit. */
commit->device_name = g_strdup(seaf->client_name);
commit->client_version = g_strdup("seadrive_"SEAFILE_CLIENT_VERSION);
seaf_repo_to_commit (repo, commit);
if (seaf_commit_manager_add_commit (seaf->commit_mgr, commit) < 0) {
ret = -1;
goto out;
}
seaf_branch_set_commit (repo->head, commit->commit_id);
repo->head->opid = last_opid;
if (seaf_branch_manager_update_branch (seaf->branch_mgr, repo->head) < 0) {
ret = -1;
}
out:
seaf_commit_unref (commit);
return ret;
}
static int
update_head_opid (SeafRepo *repo, gint64 opid)
{
repo->head->opid = opid;
if (seaf_branch_manager_update_opid (seaf->branch_mgr,
repo->id,
"local",
opid) < 0)
return -1;
if (seaf_branch_manager_update_opid (seaf->branch_mgr,
repo->id,
"master",
opid) < 0)
return -1;
return 0;
}
static int
create_commit_from_changeset (SeafRepo *repo, ChangeSet *changeset,
gint64 last_opid, const char *username,
gboolean *changed)
{
char *desc = NULL;
char *root_id = NULL;
SeafCommit *head = NULL;
GList *diff_results = NULL;
int ret = 0;
*changed = TRUE;
root_id = commit_tree_from_changeset (changeset);
if (!root_id) {
seaf_warning ("Failed to create commit tree for repo %s.\n", repo->id);
ret = -1;
goto out;
}
head = seaf_commit_manager_get_commit (seaf->commit_mgr,
repo->id, repo->version,
repo->head->commit_id);
if (!head) {
seaf_warning ("Head commit %s for repo %s not found\n",
repo->head->commit_id, repo->id);
ret = -1;
goto out;
}
if (strcmp (head->root_id, root_id) != 0) {
if (!is_repo_writable (repo)) {
seaf_warning ("Skip creating commit for read-only repo: %s.\n", repo->id);
ret = -1;
goto out;
}
/* Calculate diff for more accurate commit descriptions. */
diff_commit_roots (repo->id, repo->version, head->root_id, root_id, &diff_results, TRUE);
desc = diff_results_to_description (diff_results);
if (!desc)
desc = g_strdup("");
if (update_head_commit (repo, root_id, desc, username, last_opid) < 0) {
seaf_warning ("Failed to update head commit for repo %s.\n", repo->id);
ret = -1;
goto out;
}
} else {
*changed = FALSE;
if (update_head_opid (repo, last_opid) < 0) {
seaf_warning ("Failed to update head opid for repo %s.\n", repo->id);
ret = -1;
goto out;
}
}
out:
g_free (desc);
g_free (root_id);
seaf_commit_unref (head);
g_list_free_full (diff_results, (GDestroyNotify)diff_entry_free);
return ret;
}
static int
commit_repo (SeafRepo *repo, gboolean *changed)
{
ChangeSet *changeset;
gint64 last_opid;
SeafAccount *account;
GHashTable *updated_files = NULL;
GHashTableIter iter;
gpointer key, value;
int ret = 0;
changeset = changeset_new (repo->id);
if (!changeset) {
seaf_warning ("Failed to create changeset for repo %s.\n", repo->id);
return -1;
}
account = seaf_repo_manager_get_account (seaf->repo_mgr, repo->server, repo->user);
if (!account) {
seaf_warning ("No current account found.\n");
ret = -1;
goto out;
}
last_opid = repo->head->opid;
updated_files = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
if (apply_journal_ops_to_changeset (repo, changeset, account->username,
&last_opid, updated_files) < 0) {
seaf_warning ("Failed to apply journal operations to changeset for repo %s.\n",
repo->id);
ret = -1;
goto out;
}
/* No new operations. */
if (last_opid == repo->head->opid) {
goto out;
}
if (create_commit_from_changeset (repo, changeset, last_opid,
account->username, changed) < 0) {
ret = -1;
goto out;
}
/* Update cached file attrs after commit is done. */
char *path;
FileCacheStat *st;
g_hash_table_iter_init (&iter, updated_files);
while (g_hash_table_iter_next (&iter, &key, &value)) {
path = (char *)key;
st = (FileCacheStat *)value;
/* If mtime is 0, the file was actually not indexed. Just skip this. */
if (st->mtime != 0) {
/* Mark file content as not-yet-uploaded to server, so that
* they won't be removed by cache cleaning routine.
* They'll be marked as uploaded again when upload finishes.
*/
file_cache_mgr_set_file_uploaded (seaf->file_cache_mgr,
repo->id, path, FALSE);
repo_tree_set_file_id (repo->tree, path, st->file_id);
file_cache_mgr_set_attrs (seaf->file_cache_mgr, repo->id, path,
st->mtime, st->size, st->file_id);
}
}
out:
seaf_account_free (account);
changeset_free (changeset);
if (updated_files)
g_hash_table_destroy (updated_files);
return ret;
}
struct CommitResult {
SyncTask *task;
gboolean changed;
gboolean success;
};
static void *
commit_job (void *vtask)
{
SyncTask *task = vtask;
struct CommitResult *res = g_new0 (struct CommitResult, 1);
res->task = task;
res->success = TRUE;
if (commit_repo (task->repo, &res->changed) < 0) {
res->success = FALSE;
}
return res;
}
static void
commit_job_done (void *vres)
{
struct CommitResult *res = (struct CommitResult *)vres;
SyncTask *task = res->task;
SeafRepo *repo = task->repo;
HttpServerState *state = task->server_state;
if (repo->delete_pending) {
transition_sync_state (task, SYNC_STATE_CANCELED);
g_free (res);
return;
}
if (!res->success) {
seaf_sync_manager_set_task_error (res->task, SYNC_ERROR_COMMIT);
g_free (res);
return;
}
if (res->changed) {
char *desc = NULL;
desc = exceed_max_deleted_files (repo);
if (desc) {
notify_delete_confirmation (repo->name, desc, repo->head->commit_id);
seaf_warning ("Delete more than %d files, add delete confirmation.\n", seaf->delete_confirm_threshold);
task->info->del_confirmation_pending = TRUE;
seaf_sync_manager_set_task_error (res->task, SYNC_ERROR_DEL_CONFIRMATION_PENDING);
g_free (desc);
g_free (res);
return;
}
int rc = http_tx_manager_add_upload (seaf->http_tx_mgr,
repo->id,
repo->version,
repo->server,
repo->user,
repo->repo_uname,
state->effective_host,
repo->token,
state->http_version,
state->use_fileserver_port,
NULL);
if (rc < 0) {
seaf_warning ("Failed to add upload task for repo %s to server %s.\n",
repo->id, state->effective_host);
seaf_sync_manager_set_task_error (task, SYNC_ERROR_START_UPLOAD);
} else {
transition_sync_state (task, SYNC_STATE_UPLOAD);
}
} else {
transition_sync_state (res->task, SYNC_STATE_DONE);
}
g_free (res);
}
static gboolean
create_commit_from_journal (SyncInfo *info, HttpServerState *state, SeafRepo *repo)
{
JournalStat st;
gint now = (gint)time(NULL);
journal_get_stat (repo->journal, &st);
if (st.last_commit_time == 0 ||
(st.last_change_time >= st.last_commit_time &&
now - st.last_change_time >= 2) ||
repo->partial_commit_mode) {
if (!repo->partial_commit_mode)
journal_set_last_commit_time (repo->journal, now);
SyncTask *task = sync_task_new (info, state, NULL, NULL, repo->token, FALSE);
if (seaf_job_manager_schedule_job (seaf->job_mgr,
commit_job,
commit_job_done,
task) < 0) {
seaf_warning ("Failed to schedule commit task for repo %s.\n",
repo->id);
seaf_sync_manager_set_task_error (task, SYNC_ERROR_COMMIT);
return TRUE;
}
transition_sync_state (task, SYNC_STATE_COMMIT);
return TRUE;
}
return FALSE;
}
HttpServerInfo *
seaf_sync_manager_get_server_info (SeafSyncManager *mgr,
const char *server,
const char *user)
{
SeafAccount *account;
HttpServerState *state;
HttpServerInfo *info = NULL;
account = seaf_repo_manager_get_account (seaf->repo_mgr, server, user);
if (!account) {
return NULL;
}
state = get_http_server_state (mgr->priv, account->fileserver_addr);
seaf_account_free (account);
if (!state) {
return NULL;
}
info = g_new0 (HttpServerInfo, 1);
info->host = g_strdup (state->effective_host);
info->use_fileserver_port = state->use_fileserver_port;
return info;
}
void
seaf_sync_manager_free_server_info (HttpServerInfo *info)
{
if (!info)
return;
g_free (info->host);
g_free (info);
}
/* Path sync status. */
static ActivePathsInfo *
active_paths_info_new (SeafRepo *repo)
{
ActivePathsInfo *info = g_new0 (ActivePathsInfo, 1);
info->syncing_tree = sync_status_tree_new (repo->worktree);
info->synced_tree = sync_status_tree_new (repo->worktree);
return info;
}
static void
active_paths_info_free (ActivePathsInfo *info)
{
if (!info)
return;
sync_status_tree_free (info->syncing_tree);
sync_status_tree_free (info->synced_tree);
g_free (info);
}
void
seaf_sync_manager_update_active_path (SeafSyncManager *mgr,
const char *repo_id,
const char *path,
int mode,
SyncStatus status)
{
// Don't need to update active path on Linux.
return;
/*
ActivePathsInfo *info;
SeafRepo *repo = NULL;
if (!repo_id || !path) {
seaf_warning ("BUG: empty repo_id or path.\n");
return;
}
if (status <= SYNC_STATUS_NONE || status >= N_SYNC_STATUS) {
seaf_warning ("BUG: invalid sync status %d.\n", status);
return;
}
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo)
return;
pthread_mutex_lock (&mgr->priv->paths_lock);
info = g_hash_table_lookup (mgr->priv->active_paths, repo_id);
if (!info) {
info = active_paths_info_new (repo);
g_hash_table_insert (mgr->priv->active_paths, g_strdup(repo_id), info);
}
if (status == SYNC_STATUS_SYNCING) {
sync_status_tree_del (info->synced_tree, path);
sync_status_tree_add (info->syncing_tree, path, mode);
} else if (status == SYNC_STATUS_SYNCED) {
sync_status_tree_del (info->syncing_tree, path);
sync_status_tree_add (info->synced_tree, path, mode);
}
pthread_mutex_unlock (&mgr->priv->paths_lock);
seaf_repo_unref (repo);
*/
}
void
seaf_sync_manager_delete_active_path (SeafSyncManager *mgr,
const char *repo_id,
const char *path)
{
// Don't need to update active path on Linux.
return;
/*
ActivePathsInfo *info;
if (!repo_id || !path) {
seaf_warning ("BUG: empty repo_id or path.\n");
return;
}
pthread_mutex_lock (&mgr->priv->paths_lock);
info = g_hash_table_lookup (mgr->priv->active_paths, repo_id);
if (!info) {
pthread_mutex_unlock (&mgr->priv->paths_lock);
return;
}
sync_status_tree_del (info->syncing_tree, path);
sync_status_tree_del (info->synced_tree, path);
pthread_mutex_unlock (&mgr->priv->paths_lock);
*/
}
static char *path_status_tbl[] = {
"none",
"syncing",
"error",
"synced",
"partial_synced",
"cloud",
"readonly",
"locked",
"locked_by_me",
NULL,
};
static SyncStatus
get_repo_sync_status (SeafSyncManager *mgr, const char *repo_id)
{
ActivePathsInfo *info;
SyncStatus status = SYNC_STATUS_CLOUD;
pthread_mutex_lock (&mgr->priv->paths_lock);
info = g_hash_table_lookup (mgr->priv->active_paths, repo_id);
if (!info)
goto out;
if (sync_status_tree_has_file (info->syncing_tree))
status = SYNC_STATUS_SYNCING;
else if (sync_status_tree_has_file (info->synced_tree))
status = SYNC_STATUS_PARTIAL_SYNCED;
out:
pthread_mutex_unlock (&mgr->priv->paths_lock);
return status;
}
char *
seaf_sync_manager_get_path_sync_status (SeafSyncManager *mgr,
const char *repo_id,
const char *path)
{
ActivePathsInfo *info;
SyncStatus ret = SYNC_STATUS_NONE;
if (!repo_id || !path) {
seaf_warning ("BUG: empty repo_id or path.\n");
return NULL;
}
if (path[0] == 0) {
ret = get_repo_sync_status (mgr, repo_id);
goto check_special_states;
}
pthread_mutex_lock (&mgr->priv->paths_lock);
info = g_hash_table_lookup (mgr->priv->active_paths, repo_id);
if (!info) {
pthread_mutex_unlock (&mgr->priv->paths_lock);
ret = SYNC_STATUS_CLOUD;
goto check_special_states;
}
gboolean is_dir = FALSE;
if (sync_status_tree_exists (info->syncing_tree, path, &is_dir)) {
ret = SYNC_STATUS_SYNCING;
} else if (sync_status_tree_exists (info->synced_tree, path, &is_dir)) {
if (is_dir)
ret = SYNC_STATUS_PARTIAL_SYNCED;
else if (!file_cache_mgr_is_file_outdated (seaf->file_cache_mgr, repo_id, path))
ret = SYNC_STATUS_SYNCED;
else
ret = SYNC_STATUS_CLOUD;
} else {
SeafRepo *repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
if (!repo) {
pthread_mutex_unlock (&mgr->priv->paths_lock);
goto out;
}
RepoTreeStat st;
if (repo_tree_stat_path (repo->tree, path, &st) < 0) {
pthread_mutex_unlock (&mgr->priv->paths_lock);
seaf_repo_unref (repo);
goto out;
}
if (S_ISDIR(st.mode) ||
!file_cache_mgr_is_file_cached (seaf->file_cache_mgr, repo_id, path)) {
ret = SYNC_STATUS_CLOUD;
}
seaf_repo_unref (repo);
}
pthread_mutex_unlock (&mgr->priv->paths_lock);
check_special_states:
if (ret == SYNC_STATUS_SYNCED || ret == SYNC_STATUS_PARTIAL_SYNCED ||
ret == SYNC_STATUS_CLOUD)
{
if (!seaf_repo_manager_is_path_writable(seaf->repo_mgr, repo_id, path))
ret = SYNC_STATUS_READONLY;
else if (seaf_filelock_manager_is_file_locked_by_me (seaf->filelock_mgr,
repo_id, path))
ret = SYNC_STATUS_LOCKED_BY_ME;
else if (seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,
repo_id, path))
ret = SYNC_STATUS_LOCKED;
}
out:
return g_strdup(path_status_tbl[ret]);
}
void
seaf_sync_manager_remove_active_path_info (SeafSyncManager *mgr, const char *repo_id)
{
pthread_mutex_lock (&mgr->priv->paths_lock);
g_hash_table_remove (mgr->priv->active_paths, repo_id);
pthread_mutex_unlock (&mgr->priv->paths_lock);
}
int
seaf_sync_manager_is_syncing (SeafSyncManager *mgr)
{
GHashTableIter iter;
gpointer key, value;
SyncInfo *info;
gboolean is_syncing = FALSE;
pthread_mutex_lock (&mgr->priv->infos_lock);
g_hash_table_iter_init (&iter, mgr->priv->sync_infos);
while (g_hash_table_iter_next (&iter, &key, &value)) {
info = (SyncInfo *)value;
if (!info->in_sync || !info->current_task)
continue;
if (info->current_task->state == SYNC_STATE_UPLOAD) {
is_syncing = TRUE;
break;
}
}
pthread_mutex_unlock (&mgr->priv->infos_lock);
if (!is_syncing) {
is_syncing = file_cache_mgr_is_fetching_file (seaf->file_cache_mgr);
}
return is_syncing;
}
static int
update_tx_state_pulse (void *vmanager)
{
SeafSyncManager *mgr = vmanager;
mgr->last_sent_bytes = g_atomic_int_get (&mgr->sent_bytes);
g_atomic_int_set (&mgr->sent_bytes, 0);
mgr->last_recv_bytes = g_atomic_int_get (&mgr->recv_bytes);
g_atomic_int_set (&mgr->recv_bytes, 0);
return TRUE;
}
/* Lock/unlock files on server */
typedef struct LockFileJob {
char repo_id[37];
char *path;
gboolean lock; /* False if unlock */
} LockFileJob;
static void
lock_file_job_free (LockFileJob *job)
{
if (!job)
return;
g_free (job->path);
g_free (job);
}
static void
do_lock_file (LockFileJob *job)
{
SeafRepo *repo = NULL;
HttpServerInfo *server = NULL;
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, job->repo_id);
if (!repo)
return;
seaf_message ("Auto lock file %s/%s\n", repo->name, job->path);
int status = seaf_filelock_manager_get_lock_status (seaf->filelock_mgr,
repo->id, job->path);
if (status != FILE_NOT_LOCKED) {
goto out;
}
server = seaf_sync_manager_get_server_info (seaf->sync_mgr, repo->server, repo->user);
if (!server)
goto out;
if (http_tx_manager_lock_file (seaf->http_tx_mgr,
server->host,
server->use_fileserver_port,
repo->token,
repo->id,
job->path) < 0) {
seaf_warning ("Failed to lock %s in repo %.8s on server.\n",
job->path, repo->id);
goto out;
}
/* Mark file as locked locally so that the user can see the effect immediately. */
seaf_filelock_manager_mark_file_locked (seaf->filelock_mgr, repo->id, job->path, LOCKED_AUTO);
out:
seaf_repo_unref (repo);
seaf_sync_manager_free_server_info (server);
}
static void
do_unlock_file (LockFileJob *job)
{
SeafRepo *repo = NULL;
HttpServerInfo *server = NULL;
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, job->repo_id);
if (!repo)
return;
seaf_message ("Auto unlock file %s/%s\n", repo->name, job->path);
int status = seaf_filelock_manager_get_lock_status (seaf->filelock_mgr,
repo->id, job->path);
if (status != FILE_LOCKED_BY_ME_AUTO) {
goto out;
}
server = seaf_sync_manager_get_server_info (seaf->sync_mgr, repo->server, repo->user);
if (!server)
goto out;
if (http_tx_manager_unlock_file (seaf->http_tx_mgr,
server->host,
server->use_fileserver_port,
repo->token,
repo->id,
job->path) < 0) {
seaf_warning ("Failed to unlock %s in repo %.8s on server.\n",
job->path, repo->id);
goto out;
}
/* Mark file as unlocked locally so that the user can see the effect immediately. */
seaf_filelock_manager_mark_file_unlocked (seaf->filelock_mgr, repo->id, job->path);
out:
seaf_repo_unref (repo);
seaf_sync_manager_free_server_info (server);
}
static void *
lock_file_worker (void *vdata)
{
GAsyncQueue *queue = (GAsyncQueue *)vdata;
LockFileJob *job;
while (1) {
job = g_async_queue_pop (queue);
if (!job)
break;
if (job->lock)
do_lock_file (job);
else
do_unlock_file (job);
lock_file_job_free (job);
}
return NULL;
}
void
seaf_sync_manager_lock_file_on_server (SeafSyncManager *mgr,
const char *server,
const char *user,
const char *repo_id,
const char *path)
{
LockFileJob *job;
GAsyncQueue *queue = mgr->priv->lock_file_job_queue;
if (!seaf_repo_manager_account_is_pro (seaf->repo_mgr, server, user))
return;
job = g_new0 (LockFileJob, 1);
memcpy (job->repo_id, repo_id, 36);
job->path = g_strdup(path);
job->lock = TRUE;
g_async_queue_push (queue, job);
}
void
seaf_sync_manager_unlock_file_on_server (SeafSyncManager *mgr,
const char *server,
const char *user,
const char *repo_id,
const char *path)
{
LockFileJob *job;
GAsyncQueue *queue = mgr->priv->lock_file_job_queue;
if (!seaf_repo_manager_account_is_pro (seaf->repo_mgr, server, user))
return;
job = g_new0 (LockFileJob, 1);
memcpy (job->repo_id, repo_id, 36);
job->path = g_strdup(path);
job->lock = FALSE;
g_async_queue_push (queue, job);
}
/* Repo operations on server. */
// Create repo when mkdir in root, it contain follow procedures:
// 1. Invoke api to create repo
// 2. Get head commit from seaf-server
// 3. Create and init repo to make it can sync in local
int
seaf_sync_manager_create_repo (SeafSyncManager *mgr,
const char *server,
const char *user,
const char *repo_name)
{
SeafAccount *account;
char *resp = NULL;
gint64 resp_size;
json_t *info = NULL;
json_error_t jerror;
const char *repo_id;
const char *head_commit_id;
const char *sync_token;
HttpServerState *state;
SeafCommit *head_commit = NULL;
int ret;
char *display_name;
account = seaf_repo_manager_get_account (seaf->repo_mgr, server ,user);
if (!account)
return -1;
ret = http_tx_manager_api_create_repo (seaf->http_tx_mgr, account->server,
account->token, repo_name, &resp, &resp_size);
if (ret < 0) {
ret = -1;
goto out;
}
info = json_loadb (resp, resp_size, 0, &jerror);
if (!info) {
seaf_warning ("Invalid resp from create repo api: %s.\n", jerror.text);
ret = -1;
goto out;
}
repo_id = json_object_get_string_member (info, "repo_id");
head_commit_id = json_object_get_string_member (info, "head_commit_id");
sync_token = json_object_get_string_member (info, "token");
if (!repo_id || !head_commit_id || !sync_token) {
seaf_warning ("Invalid resp from create repo api.\n");
ret = -1;
goto out;
}
g_free (resp);
resp = NULL;
state = get_http_server_state (mgr->priv, account->fileserver_addr);
if (!state) {
seaf_warning ("Invalid http server state.\n");
ret = -1;
goto out;
}
ret = http_tx_manager_get_commit (seaf->http_tx_mgr, state->effective_host,
state->use_fileserver_port, sync_token,
repo_id, head_commit_id, &resp, &resp_size);
if (ret < 0) {
goto out;
}
head_commit = seaf_commit_from_data (head_commit_id, resp, resp_size);
if (!head_commit) {
seaf_warning ("Invalid commit info returned from server.\n");
ret = -1;
goto out;
}
if (seaf_obj_store_write_obj (seaf->commit_mgr->obj_store,
repo_id, head_commit->version,
head_commit_id, resp, (int)resp_size, TRUE) < 0) {
ret = -1;
goto out;
}
SeafRepo *repo = seaf_repo_new (repo_id, NULL, NULL);
seaf_repo_from_commit (repo, head_commit);
SeafBranch *branch = seaf_branch_new ("local", repo_id, head_commit_id, 0);
seaf_branch_manager_add_branch (seaf->branch_mgr, branch);
seaf_repo_set_head (repo, branch);
seaf_branch_unref (branch);
branch = seaf_branch_new ("master", repo_id, head_commit_id, 0);
seaf_branch_manager_add_branch (seaf->branch_mgr, branch);
seaf_branch_unref (branch);
seaf_repo_manager_set_repo_token (seaf->repo_mgr, repo, sync_token);
display_name = seaf_repo_manager_get_display_name_by_repo_name (seaf->repo_mgr, server, user, repo_name);
seaf_repo_set_worktree (repo, display_name);
g_free (display_name);
if (seaf_repo_load_fs (repo, FALSE) < 0) {
seaf_repo_free (repo);
ret = -1;
goto out;
}
repo->server = g_strdup (server);
repo->user = g_strdup (user);
seaf_repo_manager_add_repo (seaf->repo_mgr, repo);
ret = seaf_repo_manager_add_repo_to_account (seaf->repo_mgr,
account->server,
account->username,
repo);
out:
seaf_account_free (account);
if (resp)
g_free (resp);
if (info)
json_decref (info);
if (head_commit)
seaf_commit_unref (head_commit);
return ret;
}
// Rename repo when rename dir in root, it contain follow procedures:
// 1. Invoke api to rename repo
// 2. Change some internal mapping
int
seaf_sync_manager_rename_repo (SeafSyncManager *mgr,
const char *server,
const char *user,
const char *repo_id,
const char *new_name)
{
SeafAccount *account;
int ret;
account = seaf_repo_manager_get_account (seaf->repo_mgr, server, user);
if (!account)
return -1;
ret = http_tx_manager_api_rename_repo (seaf->http_tx_mgr, account->server,
account->token, repo_id, new_name);
if (ret < 0)
goto out;
ret = seaf_repo_manager_rename_repo_on_account (seaf->repo_mgr,
account->server,
account->username,
repo_id,
new_name);
if (ret < 0)
goto out;
seaf_repo_manager_rename_repo (seaf->repo_mgr, repo_id, new_name);
out:
seaf_account_free (account);
return ret;
}
int
seaf_sync_manager_delete_repo (SeafSyncManager *mgr,
const char *server,
const char *user,
const char *repo_id)
{
SeafAccount *account;
int ret;
account = seaf_repo_manager_get_account (seaf->repo_mgr, server, user);
if (!account)
return -1;
ret = http_tx_manager_api_delete_repo (seaf->http_tx_mgr, account->server,
account->token, repo_id);
if (ret < 0)
goto out;
seaf_repo_manager_remove_account_repo (seaf->repo_mgr, repo_id, server, user);
out:
seaf_account_free (account);
return ret;
}
gboolean
seaf_sync_manager_is_server_disconnected (SeafSyncManager *mgr)
{
gint disconnected;
disconnected = g_atomic_int_get (&mgr->priv->server_disconnected);
if (disconnected == 0)
return FALSE;
else
return TRUE;
}
void
seaf_sync_manager_reset_server_connected_state (SeafSyncManager *mgr)
{
g_atomic_int_set (&mgr->priv->server_disconnected, 0);
}
static void
schedule_cache_file (const char *repo_id, const char *path, RepoTreeStat *st)
{
if (S_ISREG(st->mode))
file_cache_mgr_cache_file (seaf->file_cache_mgr, repo_id, path, st);
else
file_cache_mgr_mkdir (seaf->file_cache_mgr, repo_id, path);
}
static void
check_server_connectivity (const char *server, const char *user)
{
HttpServerInfo *server_info = NULL;
while (1) {
server_info = seaf_sync_manager_get_server_info (seaf->sync_mgr, server, user);
if (server_info)
break;
seaf_sleep (1);
}
seaf_sync_manager_free_server_info (server_info);
}
static void *
cache_file_task_worker (void *vdata)
{
GAsyncQueue *task_queue = vdata;
CacheFileTask *task;
SeafRepo *repo = NULL;
while (1) {
task = g_async_queue_pop (task_queue);
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, task->repo_id);
if (!repo) {
seaf_warning ("Failed to find repo %s.\n", task->repo_id);
goto next;
}
check_server_connectivity (repo->server, repo->user);
seaf_message ("Start to cache %s/%s\n", repo->name, task->path);
repo_tree_traverse (repo->tree, task->path, schedule_cache_file);
seaf_repo_unref (repo);
next:
g_free (task->repo_id);
g_free (task->path);
g_free (task);
}
return NULL;
}
void
seaf_sync_manager_cache_path (SeafSyncManager *mgr, const char *repo_id, const char *path)
{
CacheFileTask *task = g_new0 (CacheFileTask, 1);
task->repo_id = g_strdup(repo_id);
task->path = g_strdup(path);
g_async_queue_push (mgr->priv->cache_file_task_queue, task);
}
/* static void */
/* disable_auto_sync_for_repos (SeafSyncManager *mgr) */
/* { */
/* GList *repos; */
/* GList *ptr; */
/* SeafRepo *repo; */
/* repos = seaf_repo_manager_get_repo_list (seaf->repo_mgr, -1, -1); */
/* for (ptr = repos; ptr; ptr = ptr->next) { */
/* repo = ptr->data; */
/* seaf_sync_manager_cancel_sync_task (mgr, repo->id); */
/* } */
/* g_list_free (repos); */
/* } */
int
seaf_sync_manager_disable_auto_sync (SeafSyncManager *mgr)
{
if (!seaf->started) {
seaf_message ("sync manager is not started, skip disable auto sync.\n");
return -1;
}
seaf_message ("Disabled auto sync.\n");
mgr->priv->auto_sync_enabled = FALSE;
return 0;
}
int
seaf_sync_manager_enable_auto_sync (SeafSyncManager *mgr)
{
if (!seaf->started) {
seaf_message ("sync manager is not started, skip enable auto sync.\n");
return -1;
}
seaf_message ("Enabled auto sync.\n");
mgr->priv->auto_sync_enabled = TRUE;
return 0;
}
int
seaf_sync_manager_is_auto_sync_enabled (SeafSyncManager *mgr)
{
if (mgr->priv->auto_sync_enabled)
return 1;
else
return 0;
}
seadrive-fuse-3.0.13/src/sync-mgr.h 0000664 0000000 0000000 00000021655 14761776747 0017102 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#ifndef SYNC_MGR_H
#define SYNC_MGR_H
#include
enum {
SYNC_ERROR_LEVEL_REPO,
SYNC_ERROR_LEVEL_FILE,
SYNC_ERROR_LEVEL_NETWORK,
};
#define SYNC_ERROR_ID_FILE_LOCKED_BY_APP 0
#define SYNC_ERROR_ID_FOLDER_LOCKED_BY_APP 1
#define SYNC_ERROR_ID_FILE_LOCKED 2
#define SYNC_ERROR_ID_INVALID_PATH 3
#define SYNC_ERROR_ID_INDEX_ERROR 4
#define SYNC_ERROR_ID_ACCESS_DENIED 5
#define SYNC_ERROR_ID_QUOTA_FULL 6
#define SYNC_ERROR_ID_NETWORK 7
#define SYNC_ERROR_ID_RESOLVE_PROXY 8
#define SYNC_ERROR_ID_RESOLVE_HOST 9
#define SYNC_ERROR_ID_CONNECT 10
#define SYNC_ERROR_ID_SSL 11
#define SYNC_ERROR_ID_TX 12
#define SYNC_ERROR_ID_TX_TIMEOUT 13
#define SYNC_ERROR_ID_UNHANDLED_REDIRECT 14
#define SYNC_ERROR_ID_SERVER 15
#define SYNC_ERROR_ID_LOCAL_DATA_CORRUPT 16
#define SYNC_ERROR_ID_WRITE_LOCAL_DATA 17
#define SYNC_ERROR_ID_PERM_NOT_SYNCABLE 18
#define SYNC_ERROR_ID_NO_WRITE_PERMISSION 19
#define SYNC_ERROR_ID_FOLDER_PERM_DENIED 20
#define SYNC_ERROR_ID_PATH_END_SPACE_PERIOD 21
#define SYNC_ERROR_ID_PATH_INVALID_CHARACTER 22
#define SYNC_ERROR_ID_UPDATE_TO_READ_ONLY_REPO 23
#define SYNC_ERROR_ID_CONFLICT 24
#define SYNC_ERROR_ID_UPDATE_NOT_IN_REPO 25
#define SYNC_ERROR_ID_LIBRARY_TOO_LARGE 26
#define SYNC_ERROR_ID_MOVE_NOT_IN_REPO 27
#define SYNC_ERROR_ID_DEL_CONFIRMATION_PENDING 28
#define SYNC_ERROR_ID_INVALID_PATH_ON_WINDOWS 29
#define SYNC_ERROR_ID_TOO_MANY_FILES 30
#define SYNC_ERROR_ID_BLOCK_MISSING 31
#define SYNC_ERROR_ID_CHECKOUT_FILE 32
#define SYNC_ERROR_ID_CASE_CONFLICT 33
#define SYNC_ERROR_ID_GENERAL_ERROR 34
#define SYNC_ERROR_ID_NO_ERROR 35
int
sync_error_level (int error);
typedef struct _SeafSyncManager SeafSyncManager;
typedef struct _SeafSyncManagerPriv SeafSyncManagerPriv;
struct _SeafileSession;
struct _SeafSyncManager {
struct _SeafileSession *seaf;
/* Sent/recv bytes from all transfer tasks in this second.
*/
gint sent_bytes;
gint recv_bytes;
gint last_sent_bytes;
gint last_recv_bytes;
/* Upload/download rate limits. */
gint upload_limit;
gint download_limit;
SeafSyncManagerPriv *priv;
};
SeafSyncManager* seaf_sync_manager_new (struct _SeafileSession *seaf);
int seaf_sync_manager_init (SeafSyncManager *mgr);
int seaf_sync_manager_start (SeafSyncManager *mgr);
const char *
sync_error_to_str (int error);
const char *
sync_state_to_str (int state);
typedef struct HttpServerInfo {
char *host;
gboolean use_fileserver_port;
} HttpServerInfo;
HttpServerInfo *
seaf_sync_manager_get_server_info (SeafSyncManager *mgr,
const char *server,
const char *user);
void
seaf_sync_manager_free_server_info (HttpServerInfo *info);
int
seaf_sync_manager_update_account_repo_list (SeafSyncManager *mgr,
const char *server,
const char *username);
int
seaf_sync_manager_is_syncing (SeafSyncManager *mgr);
int
seaf_handle_file_conflict (const char *repo_id,
const char *path,
gboolean *conflicted);
/* Path sync status */
enum _SyncStatus {
SYNC_STATUS_NONE = 0,
SYNC_STATUS_SYNCING,
SYNC_STATUS_ERROR,
SYNC_STATUS_SYNCED,
SYNC_STATUS_PARTIAL_SYNCED,
SYNC_STATUS_CLOUD,
SYNC_STATUS_READONLY,
SYNC_STATUS_LOCKED,
SYNC_STATUS_LOCKED_BY_ME,
N_SYNC_STATUS,
};
typedef enum _SyncStatus SyncStatus;
void
seaf_sync_manager_update_active_path (SeafSyncManager *mgr,
const char *repo_id,
const char *path,
int mode,
SyncStatus status);
void
seaf_sync_manager_delete_active_path (SeafSyncManager *mgr,
const char *repo_id,
const char *path);
char *
seaf_sync_manager_get_path_sync_status (SeafSyncManager *mgr,
const char *repo_id,
const char *path);
char *
seaf_sync_manager_list_active_paths_json (SeafSyncManager *mgr);
int
seaf_sync_manager_active_paths_number (SeafSyncManager *mgr);
void
seaf_sync_manager_remove_active_path_info (SeafSyncManager *mgr, const char *repo_id);
int
seaf_sync_manager_create_repo (SeafSyncManager *mgr,
const char *server,
const char *user,
const char *repo_name);
int
seaf_sync_manager_rename_repo (SeafSyncManager *mgr,
const char *server,
const char *user,
const char *repo_id,
const char *new_name);
int
seaf_sync_manager_delete_repo (SeafSyncManager *mgr,
const char *server,
const char *user,
const char *repo_id);
/* Lock/unlock files on server. */
void
seaf_sync_manager_lock_file_on_server (SeafSyncManager *mgr,
const char *server,
const char *user,
const char *repo_id,
const char *path);
void
seaf_sync_manager_unlock_file_on_server (SeafSyncManager *mgr,
const char *server,
const char *user,
const char *repo_id,
const char *path);
/* Sync errors */
json_t *
seaf_sync_manager_list_sync_errors (SeafSyncManager *mgr);
gboolean
seaf_sync_manager_is_server_disconnected (SeafSyncManager *mgr);
void
seaf_sync_manager_reset_server_connected_state (SeafSyncManager *mgr);
void
seaf_sync_manager_cache_path (SeafSyncManager *mgr, const char *repo_id, const char *path);
int
seaf_sync_manager_disable_auto_sync (SeafSyncManager *mgr);
int
seaf_sync_manager_enable_auto_sync (SeafSyncManager *mgr);
int
seaf_sync_manager_is_auto_sync_enabled (SeafSyncManager *mgr);
// File Cache Entry Management
typedef struct _CacheEntry {
gint64 mtime;
gint64 size;
} CacheEntry;
CacheEntry*
seaf_sync_manager_find_cache_entry (SeafSyncManager* mgr, const char* repo_id, const char* path);
int
seaf_sync_manager_handle_file_conflict (CacheEntry *entry,
const char *repo_id,
const char *fullpath,
const char *path,
gboolean *conflicted);
int
seaf_sync_manager_add_cache_entry (SeafSyncManager* mgr,
const char* repo_id, const char* path,
gint64 mtime, gint64 size);
int
seaf_sync_manager_update_cache_entry (SeafSyncManager* mgr,
const char* repo_id, const char* path,
gint64 mtime, gint64 size);
int
seaf_sync_manager_clean_cache_entries (SeafSyncManager* mgr, const char* repo_id);
int
seaf_sync_manager_del_cached_entry (SeafSyncManager* mgr, const char* repo_id, const char* path);
void
seaf_sync_manager_lock_cache_db();
void
seaf_sync_manager_unlock_cache_db();
// Rename or move entries in repo.
// old_prefix is the prefix of existing entries. They will be renamed to start with new_prefix.
// Can be used to rename a file or a folder.
int
seaf_sync_manager_rename_cache_entries (SeafSyncManager* mgr, const char* repo_id,
const char* old_prefix, const char* new_prefix);
typedef enum IgnoreReason {
IGNORE_REASON_END_SPACE_PERIOD = 0,
IGNORE_REASON_INVALID_CHARACTER = 1,
} IgnoreReason;
gboolean
seaf_sync_manager_ignored_on_checkout (const char *file_path, IgnoreReason *ignore_reason);
gboolean
seaf_repo_manager_ignored_on_commit (const char *filename);
void
seaf_sync_manager_check_locks_and_folder_perms (SeafSyncManager *manager, const char *server_url);
void
seaf_sync_manager_set_last_sync_time (SeafSyncManager *mgr,
const char *repo_id,
gint64 last_sync_time);
int
seaf_sync_manager_add_del_confirmation (SeafSyncManager *mgr,
const char *confirmation_id,
gboolean resync);
#endif
seadrive-fuse-3.0.13/src/sync-status-tree.c 0000664 0000000 0000000 00000015475 14761776747 0020573 0 ustar 00root root 0000000 0000000 #include "common.h"
#include "seafile-session.h"
#include "sync-status-tree.h"
#include "log.h"
struct _SyncStatusDir {
GHashTable *dirents; /* name -> dirent. */
};
typedef struct _SyncStatusDir SyncStatusDir;
struct _SyncStatusDirent {
char *name;
int mode;
/* Only used for directories. */
SyncStatusDir *subdir;
};
typedef struct _SyncStatusDirent SyncStatusDirent;
struct SyncStatusTree {
SyncStatusDir *root;
char *worktree;
};
typedef struct SyncStatusTree SyncStatusTree;
static void
sync_status_dirent_free (SyncStatusDirent *dirent);
static SyncStatusDir *
sync_status_dir_new ()
{
SyncStatusDir *dir = g_new0 (SyncStatusDir, 1);
dir->dirents = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free,
(GDestroyNotify)sync_status_dirent_free);
return dir;
}
static void
sync_status_dir_free (SyncStatusDir *dir)
{
if (!dir)
return;
g_hash_table_destroy (dir->dirents);
g_free (dir);
}
static SyncStatusDirent *
sync_status_dirent_new (const char *name, int mode)
{
SyncStatusDirent *dirent = g_new0(SyncStatusDirent, 1);
dirent->name = g_strdup(name);
dirent->mode = mode;
if (S_ISDIR(mode))
dirent->subdir = sync_status_dir_new ();
return dirent;
}
static void
sync_status_dirent_free (SyncStatusDirent *dirent)
{
if (!dirent)
return;
g_free (dirent->name);
sync_status_dir_free (dirent->subdir);
g_free (dirent);
}
SyncStatusTree *
sync_status_tree_new (const char *worktree)
{
SyncStatusTree *tree = g_new0(SyncStatusTree, 1);
tree->root = sync_status_dir_new ();
tree->worktree = g_strdup (worktree);
return tree;
}
#if 0
#ifdef WIN32
static void
refresh_recursive (const char *basedir, SyncStatusDir *dir)
{
GHashTableIter iter;
gpointer key, value;
char *dname, *path;
SyncStatusDirent *dirent;
g_hash_table_iter_init (&iter, dir->dirents);
while (g_hash_table_iter_next (&iter, &key, &value)) {
dname = key;
dirent = value;
path = g_strconcat(basedir, "/", dname, NULL);
seaf_sync_manager_add_refresh_path (seaf->sync_mgr, path);
if (S_ISDIR(dirent->mode))
refresh_recursive (path, dirent->subdir);
g_free (path);
}
}
#endif
#endif /* 0 */
void
sync_status_tree_free (struct SyncStatusTree *tree)
{
if (!tree)
return;
/* Free the tree recursively. */
sync_status_dir_free (tree->root);
g_free (tree->worktree);
g_free (tree);
}
void
sync_status_tree_add (SyncStatusTree *tree,
const char *path,
int mode)
{
char **dnames = NULL;
guint n, i;
char *dname;
SyncStatusDir *dir = tree->root;
SyncStatusDirent *dirent;
GString *buf;
dnames = g_strsplit (path, "/", 0);
if (!dnames)
return;
n = g_strv_length (dnames);
buf = g_string_new ("");
g_string_append (buf, tree->worktree);
for (i = 0; i < n; i++) {
dname = dnames[i];
dirent = g_hash_table_lookup (dir->dirents, dname);
g_string_append (buf, "/");
g_string_append (buf, dname);
if (dirent) {
if (S_ISDIR(dirent->mode)) {
if (i == (n-1)) {
goto out;
} else {
dir = dirent->subdir;
}
} else {
goto out;
}
} else {
if (i == (n-1)) {
dirent = sync_status_dirent_new (dname, mode);
g_hash_table_insert (dir->dirents, g_strdup(dname), dirent);
} else {
dirent = sync_status_dirent_new (dname, S_IFDIR);
g_hash_table_insert (dir->dirents, g_strdup(dname), dirent);
dir = dirent->subdir;
}
}
}
out:
g_string_free (buf, TRUE);
g_strfreev (dnames);
}
inline static gboolean
is_empty_dir (SyncStatusDirent *dirent)
{
return (g_hash_table_size(dirent->subdir->dirents) == 0);
}
static void
remove_item (SyncStatusDir *dir, const char *dname, const char *fullpath)
{
g_hash_table_remove (dir->dirents, dname);
}
static void
delete_recursive (SyncStatusDir *dir, char **dnames, guint n, guint i,
const char *base)
{
char *dname;
SyncStatusDirent *dirent;
char *fullpath = NULL;
dname = dnames[i];
fullpath = g_strconcat (base, "/", dname, NULL);
dirent = g_hash_table_lookup (dir->dirents, dname);
if (dirent) {
if (S_ISDIR(dirent->mode)) {
if (i == (n-1)) {
if (is_empty_dir(dirent))
remove_item (dir, dname, fullpath);
} else {
delete_recursive (dirent->subdir, dnames, n, ++i, fullpath);
/* If this dir becomes empty after deleting the entry below,
* remove the dir itself too.
*/
if (is_empty_dir(dirent))
remove_item (dir, dname, fullpath);
}
} else if (i == (n-1)) {
remove_item (dir, dname, fullpath);
}
}
g_free (fullpath);
}
void
sync_status_tree_del (SyncStatusTree *tree,
const char *path)
{
char **dnames = NULL;
guint n;
SyncStatusDir *dir = tree->root;
dnames = g_strsplit (path, "/", 0);
if (!dnames)
return;
n = g_strv_length (dnames);
delete_recursive (dir, dnames, n, 0, tree->worktree);
g_strfreev (dnames);
}
int
sync_status_tree_exists (SyncStatusTree *tree,
const char *path,
gboolean *is_dir)
{
char **dnames = NULL;
guint n, i;
char *dname;
SyncStatusDir *dir = tree->root;
SyncStatusDirent *dirent;
int ret = 0;
dnames = g_strsplit (path, "/", 0);
if (!dnames)
return ret;
n = g_strv_length (dnames);
for (i = 0; i < n; i++) {
dname = dnames[i];
dirent = g_hash_table_lookup (dir->dirents, dname);
if (dirent) {
if (S_ISDIR(dirent->mode)) {
if (i == (n-1)) {
if (is_dir)
*is_dir = TRUE;
ret = 1;
goto out;
} else {
dir = dirent->subdir;
}
} else {
if (i == (n-1)) {
if (is_dir)
*is_dir = FALSE;
ret = 1;
goto out;
} else {
goto out;
}
}
} else {
goto out;
}
}
out:
g_strfreev (dnames);
return ret;
}
gboolean
sync_status_tree_has_file (SyncStatusTree *tree)
{
if (!tree)
return FALSE;
return g_hash_table_size (tree->root->dirents) > 0;
}
seadrive-fuse-3.0.13/src/sync-status-tree.h 0000664 0000000 0000000 00000002057 14761776747 0020570 0 ustar 00root root 0000000 0000000 #ifndef SYNC_STATUS_TREE_H
#define SYNC_STATUS_TREE_H
struct SyncStatusTree;
struct SyncStatusTree *
sync_status_tree_new (const char *worktree);
void
sync_status_tree_free (struct SyncStatusTree *tree);
/*
* Add a @path into the @tree. If any directory along the path is missing,
* it will be created. If the path already exists, it won't be overwritten.
*/
void
sync_status_tree_add (struct SyncStatusTree *tree,
const char *path,
int mode);
/*
* Delete a path from the tree. If directory becomes empty after the deletion,
* it will be deleted too. All empty direcotries along the path will be deleted.
*/
void
sync_status_tree_del (struct SyncStatusTree *tree,
const char *path);
int
sync_status_tree_exists (struct SyncStatusTree *tree,
const char *path,
gboolean *is_dir);
// Judge whether the tree has file, to idenify whether the repo is in related sync status
gboolean
sync_status_tree_has_file (struct SyncStatusTree *tree);
#endif
seadrive-fuse-3.0.13/src/timer.c 0000664 0000000 0000000 00000003510 14761776747 0016444 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
#include
#include
#include
#else
#include
#endif
#ifdef __linux__
#include
#endif
#include "seafile-session.h"
#include "utils.h"
#include "timer.h"
struct SeafTimer
{
struct event *event;
struct timeval tv;
TimerCB func;
void *user_data;
};
static void
timer_callback (evutil_socket_t fd, short event, void *vtimer)
{
int more;
struct SeafTimer *timer = vtimer;
more = (*timer->func) (timer->user_data);
if (more)
evtimer_add (timer->event, &timer->tv);
else
seaf_timer_free (&timer);
}
void
seaf_timer_free (SeafTimer **ptimer)
{
SeafTimer *timer;
/* zero out the argument passed in */
g_return_if_fail (ptimer);
timer = *ptimer;
*ptimer = NULL;
/* destroy the timer directly or via the command queue */
if (timer)
{
event_del (timer->event);
event_free (timer->event);
g_free (timer);
}
}
struct timeval
timeval_from_msec (uint64_t milliseconds)
{
struct timeval ret;
const uint64_t microseconds = milliseconds * 1000;
ret.tv_sec = microseconds / 1000000;
ret.tv_usec = microseconds % 1000000;
return ret;
}
SeafTimer*
seaf_timer_new (TimerCB func,
void *user_data,
uint64_t interval_milliseconds)
{
SeafTimer *timer = g_new0 (SeafTimer, 1);
timer->tv = timeval_from_msec (interval_milliseconds);
timer->func = func;
timer->user_data = user_data;
timer->event = evtimer_new (seaf->ev_base, timer_callback, timer);
evtimer_add (timer->event, &timer->tv);
return timer;
}
seadrive-fuse-3.0.13/src/timer.h 0000664 0000000 0000000 00000001364 14761776747 0016456 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#ifndef SEAF_TIMER_H
#define SEAF_TIMER_H
/* return TRUE to reschedule the timer, return FALSE to cancle the timer */
typedef int (*TimerCB) (void *data);
struct SeafTimer;
typedef struct SeafTimer SeafTimer;
/**
* Calls timer_func(user_data) after the specified interval.
* The timer is freed if timer_func returns zero.
* Otherwise, it's called again after the same interval.
*/
SeafTimer* seaf_timer_new (TimerCB func,
void *user_data,
uint64_t timeout_milliseconds);
/**
* Frees a timer and sets the timer pointer to NULL.
*/
void seaf_timer_free (SeafTimer **timer);
#endif
seadrive-fuse-3.0.13/src/utils.c 0000664 0000000 0000000 00000036477 14761776747 0016506 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#ifdef HAVE_CONFIG_H
#include
#endif // HAVE_CONFIG_H
#include "common.h"
#include "utils.h"
#include "log.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifndef USE_GPL_CRYPTO
#include
#include
#include
#include
#endif
#include
#include
#include
#include
#include
void
rawdata_to_hex (const unsigned char *rawdata, char *hex_str, int n_bytes)
{
static const char hex[] = "0123456789abcdef";
int i;
for (i = 0; i < n_bytes; i++) {
unsigned int val = *rawdata++;
*hex_str++ = hex[val >> 4];
*hex_str++ = hex[val & 0xf];
}
*hex_str = '\0';
}
static unsigned hexval(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
return ~0;
}
int
hex_to_rawdata (const char *hex_str, unsigned char *rawdata, int n_bytes)
{
int i;
for (i = 0; i < n_bytes; i++) {
unsigned int val = (hexval(hex_str[0]) << 4) | hexval(hex_str[1]);
if (val & ~0xff)
return -1;
*rawdata++ = val;
hex_str += 2;
}
return 0;
}
int
checkdir_with_mkdir (const char *pathname)
{
return g_mkdir_with_parents(pathname, 0755);
}
int
seaf_stat (const char *path, SeafStat *st)
{
return stat (path, st);
}
int
seaf_fstat (int fd, SeafStat *st)
{
return fstat (fd, st);
}
int
seaf_set_file_time (const char *path, guint64 mtime)
{
struct stat st;
struct utimbuf times;
if (stat (path, &st) < 0) {
seaf_warning ("Failed to stat %s: %s.\n", path, strerror(errno));
return -1;
}
times.actime = st.st_atime;
times.modtime = (time_t)mtime;
return utime (path, ×);
}
int
seaf_util_unlink (const char *path)
{
return unlink (path);
}
int
seaf_util_rmdir (const char *path)
{
return rmdir (path);
}
int
seaf_util_mkdir (const char *path, mode_t mode)
{
return mkdir (path, mode);
}
int
seaf_util_open (const char *path, int flags)
{
return open (path, flags);
}
int
seaf_util_create (const char *path, int flags, mode_t mode)
{
return open (path, flags, mode);
}
int
seaf_util_rename (const char *oldpath, const char *newpath)
{
return rename (oldpath, newpath);
}
gboolean
seaf_util_exists (const char *path)
{
return (access (path, F_OK) == 0);
}
gint64
seaf_util_lseek (int fd, gint64 offset, int whence)
{
return lseek (fd, offset, whence);
}
gssize
seaf_getxattr (const char *path, const char *name, void *value, size_t size)
{
#ifdef __linux__
return getxattr (path, name, value, size);
#endif
#ifdef __APPLE__
return getxattr (path, name, value, size, 0, 0);
#endif
}
int
seaf_setxattr (const char *path, const char *name, const void *value, size_t size)
{
#ifdef __linux__
return setxattr (path, name, value, size, 0);
#endif
#ifdef __APPLE__
return setxattr (path, name, value, size, 0, 0);
#endif
}
int
seaf_removexattr (const char *path, const char *name)
{
#ifdef __APPLE__
return removexattr (path, name, 0);
#else
return removexattr (path, name);
#endif
}
int
seaf_truncate (const char *path, gint64 length)
{
return truncate (path, (off_t)length);
}
int
seaf_rm_recursive (const char *path)
{
SeafStat st;
int ret = 0;
GDir *dir;
const char *dname;
char *subpath;
GError *error = NULL;
if (seaf_stat (path, &st) < 0) {
seaf_warning ("Failed to stat %s: %s\n", path, strerror(errno));
return -1;
}
if (S_ISREG(st.st_mode)) {
ret = seaf_util_unlink (path);
return ret;
} else if (S_ISDIR (st.st_mode)) {
dir = g_dir_open (path, 0, &error);
if (error) {
seaf_warning ("Failed to open dir %s: %s\n", path, error->message);
return -1;
}
while ((dname = g_dir_read_name (dir)) != NULL) {
subpath = g_build_filename (path, dname, NULL);
ret = seaf_rm_recursive (subpath);
g_free (subpath);
if (ret < 0)
break;
}
g_dir_close (dir);
if (ret == 0)
ret = seaf_util_rmdir (path);
return ret;
}
return ret;
}
gssize /* Read "n" bytes from a descriptor. */
readn(int fd, void *vptr, size_t n)
{
size_t nleft;
gssize nread;
char *ptr;
ptr = vptr;
nleft = n;
while (nleft > 0) {
if ( (nread = read(fd, ptr, nleft)) < 0) {
if (errno == EINTR)
nread = 0; /* and call read() again */
else
return(-1);
} else if (nread == 0)
break; /* EOF */
nleft -= nread;
ptr += nread;
}
return(n - nleft); /* return >= 0 */
}
gssize /* Write "n" bytes to a descriptor. */
writen(int fd, const void *vptr, size_t n)
{
size_t nleft;
gssize nwritten;
const char *ptr;
ptr = vptr;
nleft = n;
while (nleft > 0) {
if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
if (nwritten < 0 && errno == EINTR)
nwritten = 0; /* and call write() again */
else
return(-1); /* error */
}
nleft -= nwritten;
ptr += nwritten;
}
return(n);
}
gssize /* Read "n" bytes from a descriptor. */
recvn(evutil_socket_t fd, void *vptr, size_t n)
{
size_t nleft;
gssize nread;
char *ptr;
ptr = vptr;
nleft = n;
while (nleft > 0) {
if ( (nread = read(fd, ptr, nleft)) < 0)
{
if (errno == EINTR) {
nread = 0; /* and call read() again */
}
else {
return(-1);
}
} else if (nread == 0)
break; /* EOF */
nleft -= nread;
ptr += nread;
}
return(n - nleft); /* return >= 0 */
}
gssize /* Write "n" bytes to a descriptor. */
sendn(evutil_socket_t fd, const void *vptr, size_t n)
{
size_t nleft;
gssize nwritten;
const char *ptr;
ptr = vptr;
nleft = n;
while (nleft > 0) {
if ( (nwritten = write(fd, ptr, nleft)) <= 0)
{
if (nwritten < 0 && errno == EINTR) {
nwritten = 0; /* and call write() again */
} else {
return -1;
}
}
nleft -= nwritten;
ptr += nwritten;
}
return(n);
}
int
seaf_pipe (seaf_pipe_t handles[2])
{
return pipe (handles);
}
int
seaf_pipe_read (seaf_pipe_t fd, char *buf, int len)
{
return read (fd, buf, len);
}
int
seaf_pipe_write (seaf_pipe_t fd, const char *buf, int len)
{
return write (fd, buf, len);
}
int
seaf_pipe_close (seaf_pipe_t fd)
{
return close (fd);
}
gssize seaf_pipe_readn (seaf_pipe_t fd, void *vptr, size_t n)
{
return readn (fd, vptr, n);
}
gssize seaf_pipe_writen (seaf_pipe_t fd, const void *vptr, size_t n)
{
return writen (fd, vptr, n);
}
void
seaf_sleep (unsigned int seconds)
{
sleep (seconds);
}
char*
ccnet_expand_path (const char *src)
{
const char *next_in, *ntoken;
char new_path[SEAF_PATH_MAX + 1];
char *next_out;
int len;
/* special cases */
if (!src || *src == '\0')
return NULL;
if (strlen(src) > SEAF_PATH_MAX)
return NULL;
next_in = src;
next_out = new_path;
*next_out = '\0';
if (*src == '~') {
/* handle src start with '~' or '~' like '~plt' */
struct passwd *pw = NULL;
for ( ; *next_in != '/' && *next_in != '\0'; next_in++) ;
len = next_in - src;
if (len == 1) {
pw = getpwuid (geteuid());
} else {
/* copy '~' to new_path */
memcpy (new_path, src, len);
new_path[len] = '\0';
pw = getpwnam (new_path + 1);
}
if (pw == NULL)
return NULL;
len = strlen (pw->pw_dir);
memcpy (new_path, pw->pw_dir, len);
next_out = new_path + len;
*next_out = '\0';
if (*next_in == '\0')
return strdup (new_path);
} else if (*src != '/') {
getcwd (new_path, SEAF_PATH_MAX);
for ( ; *next_out; next_out++) ; /* to '\0' */
}
while (*next_in != '\0') {
/* move ntoken to the next not '/' char */
for (ntoken = next_in; *ntoken == '/'; ntoken++) ;
for (next_in = ntoken; *next_in != '/'
&& *next_in != '\0'; next_in++) ;
len = next_in - ntoken;
if (len == 0) {
/* the path ends with '/', keep it */
*next_out++ = '/';
*next_out = '\0';
break;
}
if (len == 2 && ntoken[0] == '.' && ntoken[1] == '.')
{
/* '..' */
for (; next_out > new_path && *next_out != '/'; next_out--)
;
*next_out = '\0';
} else if (ntoken[0] != '.' || len != 1) {
/* not '.' */
*next_out++ = '/';
memcpy (next_out, ntoken, len);
next_out += len;
*next_out = '\0';
}
}
/* the final special case */
if (new_path[0] == '\0') {
new_path[0] = '/';
new_path[1] = '\0';
}
return strdup (new_path);
}
int
calculate_sha1 (unsigned char *sha1, const char *msg, int len)
{
GChecksum *c;
gsize cs_len = 20;
if (len < 0)
len = strlen(msg);
c = g_checksum_new (G_CHECKSUM_SHA1);
g_checksum_update(c, (const unsigned char *)msg, len);
g_checksum_get_digest (c, sha1, &cs_len);
g_checksum_free (c);
return 0;
}
uint32_t
ccnet_sha1_hash (const void *v)
{
/* 31 bit hash function */
const unsigned char *p = v;
uint32_t h = 0;
int i;
for (i = 0; i < 20; i++)
h = (h << 5) - h + p[i];
return h;
}
int
ccnet_sha1_equal (const void *v1,
const void *v2)
{
const unsigned char *p1 = v1;
const unsigned char *p2 = v2;
int i;
for (i = 0; i < 20; i++)
if (p1[i] != p2[i])
return 0;
return 1;
}
char* gen_uuid ()
{
char *uuid_str = g_malloc (37);
uuid_t uuid;
uuid_generate (uuid);
uuid_unparse_lower (uuid, uuid_str);
return uuid_str;
}
void gen_uuid_inplace (char *buf)
{
uuid_t uuid;
uuid_generate (uuid);
uuid_unparse_lower (uuid, buf);
}
gboolean
is_uuid_valid (const char *uuid_str)
{
uuid_t uuid;
if (!uuid_str)
return FALSE;
if (uuid_parse (uuid_str, uuid) < 0)
return FALSE;
return TRUE;
}
gboolean
is_object_id_valid (const char *obj_id)
{
if (!obj_id)
return FALSE;
int len = strlen(obj_id);
int i;
char c;
if (len != 40)
return FALSE;
for (i = 0; i < len; ++i) {
c = obj_id[i];
if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'))
continue;
return FALSE;
}
return TRUE;
}
void
string_list_free (GList *str_list)
{
GList *ptr = str_list;
while (ptr) {
g_free (ptr->data);
ptr = ptr->next;
}
g_list_free (str_list);
}
/* JSON related utils. For compatibility with json-glib. */
const char *
json_object_get_string_member (json_t *object, const char *key)
{
json_t *string = json_object_get (object, key);
if (!string)
return NULL;
return json_string_value (string);
}
gboolean
json_object_has_member (json_t *object, const char *key)
{
return (json_object_get (object, key) != NULL);
}
gint64
json_object_get_int_member (json_t *object, const char *key)
{
json_t *integer = json_object_get (object, key);
return json_integer_value (integer);
}
void
json_object_set_string_member (json_t *object, const char *key, const char *value)
{
json_object_set_new (object, key, json_string (value));
}
void
json_object_set_int_member (json_t *object, const char *key, gint64 value)
{
json_object_set_new (object, key, json_integer (value));
}
void
clean_utf8_data (char *data, int len)
{
const char *s, *e;
char *p;
gboolean is_valid;
s = data;
p = data;
while ((s - data) != len) {
is_valid = g_utf8_validate (s, len - (s - data), &e);
if (is_valid)
break;
if (s != e)
p += (e - s);
*p = '?';
++p;
s = e + 1;
}
}
/* zlib related wrapper functions. */
#define ZLIB_BUF_SIZE 16384
int
seaf_compress (guint8 *input, int inlen, guint8 **output, int *outlen)
{
int ret;
unsigned have;
z_stream strm;
guint8 out[ZLIB_BUF_SIZE];
GByteArray *barray;
if (inlen == 0)
return -1;
/* allocate deflate state */
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
ret = deflateInit(&strm, Z_DEFAULT_COMPRESSION);
if (ret != Z_OK) {
seaf_warning ("deflateInit failed.\n");
return -1;
}
strm.avail_in = inlen;
strm.next_in = input;
barray = g_byte_array_new ();
do {
strm.avail_out = ZLIB_BUF_SIZE;
strm.next_out = out;
ret = deflate(&strm, Z_FINISH); /* no bad return value */
have = ZLIB_BUF_SIZE - strm.avail_out;
g_byte_array_append (barray, out, have);
} while (ret != Z_STREAM_END);
*outlen = barray->len;
*output = g_byte_array_free (barray, FALSE);
/* clean up and return */
(void)deflateEnd(&strm);
return 0;
}
int
seaf_decompress (guint8 *input, int inlen, guint8 **output, int *outlen)
{
int ret;
unsigned have;
z_stream strm;
unsigned char out[ZLIB_BUF_SIZE];
GByteArray *barray;
if (inlen == 0) {
seaf_warning ("Empty input for zlib, invalid.\n");
return -1;
}
/* allocate inflate state */
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.avail_in = 0;
strm.next_in = Z_NULL;
ret = inflateInit(&strm);
if (ret != Z_OK) {
seaf_warning ("inflateInit failed.\n");
return -1;
}
strm.avail_in = inlen;
strm.next_in = input;
barray = g_byte_array_new ();
do {
strm.avail_out = ZLIB_BUF_SIZE;
strm.next_out = out;
ret = inflate(&strm, Z_NO_FLUSH);
if (ret < 0) {
seaf_warning ("Failed to inflate.\n");
goto out;
}
have = ZLIB_BUF_SIZE - strm.avail_out;
g_byte_array_append (barray, out, have);
} while (ret != Z_STREAM_END);
out:
/* clean up and return */
(void)inflateEnd(&strm);
if (ret == Z_STREAM_END) {
*outlen = barray->len;
*output = g_byte_array_free (barray, FALSE);
return 0;
} else {
g_byte_array_free (barray, TRUE);
return -1;
}
}
char*
format_path (const char *path)
{
int path_len = strlen (path);
char *rpath;
if (path[0] == '/') {
rpath = g_strdup (path + 1);
path_len--;
} else {
rpath = g_strdup (path);
}
while (path_len > 1 && rpath[path_len-1] == '/') {
rpath[path_len-1] = '\0';
path_len--;
}
return rpath;
}
char *
parse_fileserver_addr (const char *server_addr)
{
char *location = strstr(server_addr, "//");
if (!location)
return NULL;
location += 2;
char *sep = strchr (location, '/');
if (!sep) {
return g_strdup(server_addr);
} else {
return g_strndup (server_addr, sep - server_addr);
}
}
seadrive-fuse-3.0.13/src/utils.h 0000664 0000000 0000000 00000013016 14761776747 0016473 0 ustar 00root root 0000000 0000000 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#ifndef CCNET_UTILS_H
#define CCNET_UTILS_H
#include
#include
#include
#include
#include
#include
#include