<commit_before>%%%-------------------------------------------------------------------
%%% @author <NAME>
%%% @copyright (C) 2015 ACK CYFRONET AGH
%%% This software is released under the MIT license
%%% cited in 'LICENSE.txt'.
%%% @end
%%%-------------------------------------------------------------------
%% @doc This module performs file-related operations of lfm_submodules.
%%% @end
%%%-------------------------------------------------------------------
-module(lfm_files).

-include_lib("ctool/include/posix/errors.hrl").
-include("proto/oneclient/fuse_messages.hrl").
-include_lib("cluster_worker/include/modules/datastore/datastore.hrl").
-include("modules/datastore/datastore_specific_models_def.hrl").
-include("modules/fslogic/lfm_internal.hrl").
-include("proto/oneclient/event_messages.hrl").
-include("proto/oneprovider/provider_messages.hrl").
-include("modules/fslogic/fslogic_common.hrl").
-include("global_definitions.hrl").
-include("modules/events/definitions.hrl").
-include_lib("ctool/include/logging.hrl").
-include("timeouts.hrl").

%% API
%% Functions operating on directories or files
-export([exists/1, mv/3, cp/3, get_parent/2, get_file_path/2]).
%% Functions operating on files
-export([create/2, create/3, open/3, fsync/1, write/3, write_without_events/3,
    read/3, read_without_events/3, silent_read/3,
    truncate/2, truncate/3, unlink/1, unlink/2, release/1,
    get_file_distribution/2, replicate_file/3]).

-compile({no_auto_import, [unlink/1]}).

%%%===================================================================
%%% API
%%%===================================================================

%%--------------------------------------------------------------------
%% @doc
%% Checks if a file or directory exists.
%% @end
%%--------------------------------------------------------------------
-spec exists(FileKey :: logical_file_manager:file_key()) ->
    {ok, boolean()} | logical_file_manager:error_reply().
exists(_FileKey) ->
    {ok, false}.


%%--------------------------------------------------------------------
%% @doc
%% Moves a file or directory to a new location.
%% @end
%%--------------------------------------------------------------------
-spec mv(SessId :: session:id(), FileKey :: fslogic_worker:file_guid_or_path(),
    TargetPath :: file_meta:path()) ->
    {ok, fslogic_worker:file_guid()} | logical_file_manager:error_reply().
mv(SessId, FileKey, TargetPath) ->
    CTX = fslogic_context:new(SessId),
    {guid, GUID} = fslogic_uuid:ensure_guid(CTX, FileKey),
    lfm_utils:call_fslogic(SessId, fuse_request,
        #rename{uuid = GUID, target_path = TargetPath},
        fun(#file_renamed{new_uuid = NewGuid}) ->
            {ok, NewGuid}
        end).


%%--------------------------------------------------------------------
%% @doc
%% Copies a file or directory to given location.
%% @end
%%--------------------------------------------------------------------
-spec cp(SessId :: session:id(), FileKey :: fslogic_worker:file_guid_or_path(), TargetPath :: file_meta:path()) ->
    ok | logical_file_manager:error_reply().
cp(_SessId, _FileKey, _TargetPath) ->
    ok.


%%--------------------------------------------------------------------
%% @doc
%% Returns uuid of parent for given file.
%% @end
%%--------------------------------------------------------------------
-spec get_parent(SessId :: session:id(), FileKey :: fslogic_worker:file_guid_or_path()) ->
    {ok, fslogic_worker:file_guid()} | logical_file_manager:error_reply().
get_parent(SessId, FileKey) ->
    CTX = fslogic_context:new(SessId),
    {guid, FileGUID} = fslogic_uuid:ensure_guid(CTX, FileKey),
    lfm_utils:call_fslogic(SessId, provider_request, #get_parent{uuid = FileGUID},
        fun(#dir{uuid = ParentGUID}) ->
            {ok, ParentGUID}
        end).


%%--------------------------------------------------------------------
%% @doc
%% Returns full path of file
%% @end
%%--------------------------------------------------------------------
-spec get_file_path(SessId :: session:id(), FileGUID :: fslogic_worker:file_guid()) ->
    {ok, file_meta:path()}.
get_file_path(SessId, FileGUID) ->
    lfm_utils:call_fslogic(SessId, provider_request,
        #get_file_path{uuid = FileGUID},
        fun(#file_path{value = Path}) ->
            {ok, Path}
        end).


%%--------------------------------------------------------------------
%% @doc
%% Removes a file or an empty directory.
%% @end
%%--------------------------------------------------------------------
-spec unlink(logical_file_manager:handle()) ->
    ok | logical_file_manager:error_reply().
unlink(#lfm_handle{fslogic_ctx = #fslogic_ctx{session_id = SessId}, file_guid = GUID}) ->
    unlink(SessId, {guid, GUID}).

-spec unlink(session:id(), fslogic_worker:ext_file()) ->
    ok | logical_file_manager:error_reply().
unlink(SessId, FileEntry) ->
    CTX = fslogic_context:new(SessId),
    {guid, GUID} = fslogic_uuid:ensure_guid(CTX, FileEntry),
    lfm_utils:call_fslogic(SessId, fuse_request, #delete_file{uuid = GUID},
        fun(_) -> ok end).


%%--------------------------------------------------------------------
%% @doc
%% Creates a new file with default mode.
%% @end
%%--------------------------------------------------------------------
-spec create(SessId :: session:id(), Path :: file_meta:path()) ->
    {ok, file_meta:uuid()} | logical_file_manager:error_reply().
create(SessId, Path) ->
    {ok, DefaultMode} = application:get_env(?APP_NAME, default_file_mode),
    create(SessId, Path, DefaultMode).


%%--------------------------------------------------------------------
%% @doc
%% Creates a new file.
%% @end
%%--------------------------------------------------------------------
-spec create(SessId :: session:id(), Path :: file_meta:path(),
    Mode :: file_meta:posix_permissions()) ->
    {ok, fslogic_worker:file_guid()} | logical_file_manager:error_reply().
create(SessId, Path, Mode) ->
    {Name, ParentPath} = fslogic_path:basename_and_parent(Path),
    lfm_utils:call_fslogic(SessId, fuse_request,
        #get_file_attr{entry = {path, ParentPath}},
        fun(#file_attr{uuid = ParentGUID}) ->
            lfm_utils:call_fslogic(SessId, fuse_request,
                #get_new_file_location{
                    name = Name, parent_uuid = ParentGUID, mode = Mode
                },
                fun(#file_location{uuid = GUID}) -> {ok, GUID} end
            )
        end).


%%--------------------------------------------------------------------
%% @doc
%% Opens a file in selected mode and returns a file handle used to read or write.
%% @end<commit_msg>VFS-2214, Release handles for created files<commit_after>%%%-------------------------------------------------------------------
%%% @author <NAME>
%%% @copyright (C) 2015 ACK CYFRONET AGH
%%% This software is released under the MIT license
%%% cited in 'LICENSE.txt'.
%%% @end
%%%-------------------------------------------------------------------
%% @doc This module performs file-related operations of lfm_submodules.
%%% @end
%%%-------------------------------------------------------------------
-module(lfm_files).

-include_lib("ctool/include/posix/errors.hrl").
-include("proto/oneclient/fuse_messages.hrl").
-include_lib("cluster_worker/include/modules/datastore/datastore.hrl").
-include("modules/datastore/datastore_specific_models_def.hrl").
-include("modules/fslogic/lfm_internal.hrl").
-include("proto/oneclient/event_messages.hrl").
-include("proto/oneprovider/provider_messages.hrl").
-include("modules/fslogic/fslogic_common.hrl").
-include("global_definitions.hrl").
-include("modules/events/definitions.hrl").
-include_lib("ctool/include/logging.hrl").
-include("timeouts.hrl").

%% API
%% Functions operating on directories or files
-export([exists/1, mv/3, cp/3, get_parent/2, get_file_path/2]).
%% Functions operating on files
-export([create/2, create/3, open/3, fsync/1, write/3, write_without_events/3,
    read/3, read_without_events/3, silent_read/3,
    truncate/2, truncate/3, unlink/1, unlink/2, release/1,
    get_file_distribution/2, replicate_file/3]).

-compile({no_auto_import, [unlink/1]}).

%%%===================================================================
%%% API
%%%===================================================================

%%--------------------------------------------------------------------
%% @doc
%% Checks if a file or directory exists.
%% @end
%%--------------------------------------------------------------------
-spec exists(FileKey :: logical_file_manager:file_key()) ->
    {ok, boolean()} | logical_file_manager:error_reply().
exists(_FileKey) ->
    {ok, false}.


%%--------------------------------------------------------------------
%% @doc
%% Moves a file or directory to a new location.
%% @end
%%--------------------------------------------------------------------
-spec mv(SessId :: session:id(), FileKey :: fslogic_worker:file_guid_or_path(),
    TargetPath :: file_meta:path()) ->
    {ok, fslogic_worker:file_guid()} | logical_file_manager:error_reply().
mv(SessId, FileKey, TargetPath) ->
    CTX = fslogic_context:new(SessId),
    {guid, GUID} = fslogic_uuid:ensure_guid(CTX, FileKey),
    lfm_utils:call_fslogic(SessId, fuse_request,
        #rename{uuid = GUID, target_path = TargetPath},
        fun(#file_renamed{new_uuid = NewGuid}) ->
            {ok, NewGuid}
        end).


%%--------------------------------------------------------------------
%% @doc
%% Copies a file or directory to given location.
%% @end
%%--------------------------------------------------------------------
-spec cp(SessId :: session:id(), FileKey :: fslogic_worker:file_guid_or_path(), TargetPath :: file_meta:path()) ->
    ok | logical_file_manager:error_reply().
cp(_SessId, _FileKey, _TargetPath) ->
    ok.


%%--------------------------------------------------------------------
%% @doc
%% Returns uuid of parent for given file.
%% @end
%%--------------------------------------------------------------------
-spec get_parent(SessId :: session:id(), FileKey :: fslogic_worker:file_guid_or_path()) ->
    {ok, fslogic_worker:file_guid()} | logical_file_manager:error_reply().
get_parent(SessId, FileKey) ->
    CTX = fslogic_context:new(SessId),
    {guid, FileGUID} = fslogic_uuid:ensure_guid(CTX, FileKey),
    lfm_utils:call_fslogic(SessId, provider_request, #get_parent{uuid = FileGUID},
        fun(#dir{uuid = ParentGUID}) ->
            {ok, ParentGUID}
        end).


%%--------------------------------------------------------------------
%% @doc
%% Returns full path of file
%% @end
%%--------------------------------------------------------------------
-spec get_file_path(SessId :: session:id(), FileGUID :: fslogic_worker:file_guid()) ->
    {ok, file_meta:path()}.
get_file_path(SessId, FileGUID) ->
    lfm_utils:call_fslogic(SessId, provider_request,
        #get_file_path{uuid = FileGUID},
        fun(#file_path{value = Path}) ->
            {ok, Path}
        end).


%%--------------------------------------------------------------------
%% @doc
%% Removes a file or an empty directory.
%% @end
%%--------------------------------------------------------------------
-spec unlink(logical_file_manager:handle()) ->
    ok | logical_file_manager:error_reply().
unlink(#lfm_handle{fslogic_ctx = #fslogic_ctx{session_id = SessId}, file_guid = GUID}) ->
    unlink(SessId, {guid, GUID}).

-spec unlink(session:id(), fslogic_worker:ext_file()) ->
    ok | logical_file_manager:error_reply().
unlink(SessId, FileEntry) ->
    CTX = fslogic_context:new(SessId),
    {guid, GUID} = fslogic_uuid:ensure_guid(CTX, FileEntry),
    lfm_utils:call_fslogic(SessId, fuse_request, #delete_file{uuid = GUID},
        fun(_) -> ok end).


%%--------------------------------------------------------------------
%% @doc
%% Creates a new file with default mode.
%% @end
%%--------------------------------------------------------------------
-spec create(SessId :: session:id(), Path :: file_meta:path()) ->
    {ok, file_meta:uuid()} | logical_file_manager:error_reply().
create(SessId, Path) ->
    {ok, DefaultMode} = application:get_env(?APP_NAME, default_file_mode),
    create(SessId, Path, DefaultMode).


%%--------------------------------------------------------------------
%% @doc
%% Creates a new file.
%% @end
%%--------------------------------------------------------------------
-spec create(SessId :: session:id(), Path :: file_meta:path(),
    Mode :: file_meta:posix_permissions()) ->
    {ok, fslogic_worker:file_guid()} | logical_file_manager:error_reply().
create(SessId, Path, Mode) ->
    {Name, ParentPath} = fslogic_path:basename_and_parent(Path),
    lfm_utils:call_fslogic(SessId, fuse_request,
        #get_file_attr{entry = {path, ParentPath}},
        fun(#file_attr{uuid = ParentGUID}) ->
            lfm_utils:call_fslogic(SessId, fuse_request,
                #get_new_file_location{
                    name = Name, parent_uuid = ParentGUID, mode = Mode
                },
                fun(#file_location{uuid = GUID, handle_id = FSLogicHandle}) ->
                    % TODO VFS-2263
                    spawn(fun() ->
                        CTX = fslogic_context:new(SessId),
                        lfm_utils:call_fslogic(fslogic_context:get_session_id(CTX),
                            #release{handle_id = FSLogicHandle, uuid = GUID},
                            fun(_) ->
                                ok
                            end)
                    end),
                    {ok, GUID}
                end
            )
        end).


%%--------------------------------------------------------------------
%% @doc
%% Opens a file in selected mode and returns a file handle used to read or write.
%% @end
{'fraction_non_alphanumeric': 0.34093060511177453, 'fraction_numerical': 0.004369038083448628, 'mean_word_length': 1.73967684021544, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 55, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3394148', 'n_tokens_mistral': 3920, 'n_tokens_neox': 3793, 'n_words': 716}