Welcome to mirror list, hosted at ThFree Co, Russian Federation.

file_location.rs « src « file « generic - gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 788354541ecd337a1ad3b93f09ec2028e2f0916a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// Copyright (C) 2016-2017 Sebastian Dröge <sebastian@centricular.com>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// SPDX-License-Identifier: MIT OR Apache-2.0
use gst::glib;
use url::Url;

use std::fmt;
use std::ops::Deref;
use std::path::{Path, PathBuf};

#[cfg(target_os = "windows")]
const WIN_EXT_PATH_PREFIX: &str = "\\\\?\\";
#[cfg(target_os = "windows")]
const WIN_EXT_PATH_PREFIX_LEN: usize = 4;

#[derive(Debug)]
pub(super) struct FileLocation(PathBuf);

impl FileLocation {
    pub(super) fn try_from_path_str(path_str: String) -> Result<Self, glib::Error> {
        FileLocation::try_from(PathBuf::from(path_str))
    }

    pub(super) fn try_from_uri_str(uri_str: &str) -> Result<Self, glib::Error> {
        match Url::parse(uri_str) {
            Ok(url) => {
                if url.scheme() != "file" {
                    return Err(glib::Error::new(
                        gst::URIError::UnsupportedProtocol,
                        format!("Unsupported URI {uri_str}").as_str(),
                    ));
                }

                let path = url.to_file_path().map_err(|_| {
                    glib::Error::new(
                        gst::URIError::BadUri,
                        format!("Unsupported URI {uri_str}").as_str(),
                    )
                })?;

                FileLocation::try_from(path)
            }
            Err(err) => Err(glib::Error::new(
                gst::URIError::BadUri,
                format!("Couldn't parse URI {uri_str}: {err}").as_str(),
            )),
        }
    }

    fn try_from(location: PathBuf) -> Result<Self, glib::Error> {
        let location_str = location.to_str().ok_or_else(|| {
            glib::Error::new(
                gst::URIError::BadReference,
                format!("Invalid path {location:?}").as_str(),
            )
        })?;

        let file_name = location.file_name().ok_or_else(|| {
            glib::Error::new(
                gst::URIError::BadReference,
                format!("Expected a path with a filename, got {location_str}",).as_str(),
            )
        })?;

        // The filename might not exist yet, so check the parent only.
        // Note: `location` contains a filename, so its parent can't be `None`
        let mut parent_dir = location
            .parent()
            .expect("FileSink::set_location `location` with filename but without a parent")
            .to_owned();
        if parent_dir.is_relative() && parent_dir.components().next().is_none() {
            // `location` only contains the filename
            // need to specify "." for `canonicalize` to resolve the actual path
            parent_dir = PathBuf::from(".");
        }

        let parent_canonical = parent_dir.canonicalize().map_err(|err| {
            glib::Error::new(
                gst::URIError::BadReference,
                format!("Could not resolve path {location_str}: {err}",).as_str(),
            )
        })?;

        #[cfg(target_os = "windows")]
        let parent_canonical = {
            let has_prefix = parent_canonical
                .to_str()
                .unwrap() // already checked above
                .starts_with(WIN_EXT_PATH_PREFIX);
            if has_prefix {
                // Remove the "extended length path" prefix
                // for compatibility with applications which can't deal with it.
                // See https://doc.rust-lang.org/std/fs/fn.canonicalize.html
                let parent_canonical_str = parent_canonical.to_str().unwrap();
                PathBuf::from(&parent_canonical_str[WIN_EXT_PATH_PREFIX_LEN..])
            } else {
                parent_canonical
            }
        };

        let location_canonical = parent_canonical.join(file_name);
        Url::from_file_path(&location_canonical)
            .map_err(|_| {
                glib::Error::new(
                    gst::URIError::BadReference,
                    format!("Could not resolve path to URL {location_str}").as_str(),
                )
            })
            .map(|_| FileLocation(location_canonical))
    }

    fn to_str(&self) -> &str {
        self.0
            .to_str()
            .expect("FileLocation: couldn't get `&str` from internal `PathBuf`")
    }
}

impl AsRef<Path> for FileLocation {
    fn as_ref(&self) -> &Path {
        self.0.as_ref()
    }
}

impl Deref for FileLocation {
    type Target = Path;

    fn deref(&self) -> &Path {
        self.0.as_ref()
    }
}

impl fmt::Display for FileLocation {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.to_str())
    }
}