/// <summary> /// Find a share that allows access to this local path. /// Note that it's possible for more than one share to match a specific path. /// In such case the most specific share will be chosen. /// For example if share A has local path /home/user and share B has local /// path /home/user/downloads, for localPath='/home/user/downloads/file.mp4' /// the method returns B. /// </summary> /// <returns>The parent share or null.</returns> /// <param name="localPath">Local absolute path of a file or directory. /// It doesn't necessarily need to exist, but needs to be a valid path. /// It should be a plain path, not an URI or UNC path and shouldn't be /// escaped in any way. /// </param> public Share FindParentShare(TokenizedLocalPath localPath) { Share result = null; ShareTreeEntry treeElem = treeRoot; foreach (string elem in localPath) { string elemCased = caseSensitiveFileSystem ? elem : elem.ToLowerInvariant(); ShareTreeEntry childElem = null; treeElem.children.TryGetValue(elemCased, out childElem); if (childElem != null) { if (childElem.share != null) { // Found a matching share! But continue searching in case // there's another, more specific share. result = childElem.share; } // Continue diving into the tree. treeElem = childElem; } else { // No more children. break; } } return(result); }
/// <summary> /// Add the share to this list. If for any reason there's already another /// share for this local path, the new share replaces it. /// </summary> /// <param name="share">The share to add.</param> public void AddOrReplace(Share share) { if (!sharesByName.ContainsKey(share.Name)) { sharesByName.Remove(share.Name); } sharesByName.Add(share.Name, share); TokenizedLocalPath path = share.LocalPath; ShareTreeEntry treeElem = treeRoot; foreach (string elem in share.LocalPath) { string elemCased = caseSensitiveFileSystem ? elem : elem.ToLowerInvariant(); ShareTreeEntry childElem = null; treeElem.children.TryGetValue(elemCased, out childElem); if (childElem == null) { childElem = new ShareTreeEntry(elem); treeElem.children.Add(elemCased, childElem); } treeElem = childElem; } treeElem.share = share; }
public override bool Equals(Object obj) { if (obj == null || GetType() != obj.GetType()) { return(false); } TokenizedLocalPath other = (TokenizedLocalPath)obj; return(Array.Equals(this.pathElements, other.pathElements)); }
public static TokenizedLocalPath Combine(TokenizedLocalPath parentPath, TokenizedLocalPath relativePath) { if (relativePath == null || relativePath.IsEmpty()) { return(parentPath); } string[] parentElems = parentPath.pathElements; string[] relativeElems = relativePath.pathElements; string[] newElems = new string[parentElems.Length + relativeElems.Length]; Array.Copy(parentElems, newElems, parentElems.Length); Array.Copy(relativeElems, 0, newElems, parentElems.Length, relativeElems.Length); return(new TokenizedLocalPath(newElems)); }
public static string MakeLink(LinkFormat linkFormat, string host, SharesList shares, TokenizedLocalPath localPath, char systemSeparator) { if (linkFormat == LinkFormat.LocalFile || linkFormat == LinkFormat.LocalPath) { return(ComposeLink(linkFormat, host, null, localPath, systemSeparator)); } else { Share share = shares.FindParentShare(localPath); if (share != null) { TokenizedLocalPath relPath = TokenizedLocalPath.MakeRelative(share.LocalPath, localPath, shares.CaseSensitiveFileSystem); return(ComposeLink(linkFormat, host, share, relPath, systemSeparator)); } else { return(null); } } }
public static TokenizedLocalPath MakeRelative(TokenizedLocalPath parentPath, TokenizedLocalPath childPath, bool caseSensitiveFileSystem) { string[] parentElems = parentPath.pathElements; string[] childElems = childPath.pathElements; // Check if the child path is really relative to parent. StringComparison comparision = caseSensitiveFileSystem ? StringComparison.InvariantCulture : StringComparison.InvariantCultureIgnoreCase; for (int i = 0; i < parentElems.Length; i++) { string e1 = parentElems[i]; string e2 = childElems[i]; if (!string.Equals(e1, e2, comparision)) { throw new ArgumentException(string.Format("child path {0} is not relative to parent {1}", childPath.ToString(), parentPath.ToString())); } } string[] relativeElems = new string[childElems.Length - parentElems.Length]; Array.Copy(childElems, parentElems.Length, relativeElems, 0, relativeElems.Length); return(new TokenizedLocalPath(relativeElems)); }
public static string ComposeLink(LinkFormat linkFormat, string host, Share share, TokenizedLocalPath shareRelativePath, char systemSeparator) { StringBuilder sb = new StringBuilder(); if (linkFormat == LinkFormat.Smb) { sb.Append("smb://"); sb.Append(Uri.EscapeUriString(host)); sb.Append('/'); sb.Append(share.NameEscaped); if (shareRelativePath != null && !shareRelativePath.IsEmpty()) { sb.Append('/'); shareRelativePath.Format(sb, true, '/'); } } else if (linkFormat == LinkFormat.Unc) { sb.Append("\\\\"); sb.Append(host); sb.Append('\\'); sb.Append(share.Name); if (shareRelativePath != null && !shareRelativePath.IsEmpty()) { sb.Append('\\'); shareRelativePath.Format(sb, false, '\\'); } } else if (linkFormat == LinkFormat.UncEscaped) { sb.Append("\\\\"); sb.Append(Uri.EscapeUriString(host)); sb.Append('\\'); sb.Append(share.NameEscaped); if (shareRelativePath != null && !shareRelativePath.IsEmpty()) { sb.Append('\\'); shareRelativePath.Format(sb, true, '\\'); } } else if (linkFormat == LinkFormat.File) { sb.Append("file://"); sb.Append(Uri.EscapeUriString(host)); sb.Append('/'); sb.Append(share.NameEscaped); if (shareRelativePath != null && !shareRelativePath.IsEmpty()) { sb.Append('/'); shareRelativePath.Format(sb, true, '/'); } } else if (linkFormat == LinkFormat.LocalFile) { sb.Append("file://"); if (share != null) { share.LocalPath.Format(sb, true, '/'); } if (shareRelativePath != null && !shareRelativePath.IsEmpty()) { if (share != null) { sb.Append('/'); } shareRelativePath.Format(sb, true, '/'); } } else if (linkFormat == LinkFormat.LocalPath) { if (share != null) { share.LocalPath.Format(sb, false, systemSeparator); } if (shareRelativePath != null && !shareRelativePath.IsEmpty()) { if (share != null) { sb.Append(systemSeparator); } shareRelativePath.Format(sb, false, systemSeparator); } } else { throw new Exception("unexpected link format: " + linkFormat); } string link = sb.ToString(); return(link); }
[STAThread] // for Windows.Forms, which is used by clipboard public static int Main(string[] args) { // Failed unless otherwise reached the right point. int exitCode = 1; string host = null; string sharesfile = null; bool showHelp = false; bool showVersion = false; LinkFormat linkFormat = LinkFormat.Unc; HostType hostType = HostType.Netbios; // Not implemented for now to keep things simple. The problem with the clipboard is that it's normally tied to a widget toolkit. // Other than that, easy peasy. bool copyToClipboard = false; bool readStdIn = false; bool noStdErr = false; // I don't really like changing local variables from lambdas, but it makes the code short. // If I get to know other command line parsing libs I may replace Mono.Options in the future.. maybe. var options = new OptionSet() { { "host=", "Custom hostname.", (string v) => host = v }, { "sharesfile=", "Provide a custom .ini file with share list. The format should correspond to outout of \"net usershare info\" command, which also matches smb.conf share list.", (string v) => sharesfile = v }, { "format=", "Link format. Either smb or unc. Default is " + linkFormat + ".", (LinkFormat v) => linkFormat = v }, { "hosttype=", "One of ip, ip6, netbios or hostname. Default is " + hostType + ". Ignored if custom host is specified.", (HostType v) => hostType = v }, { "help", "Show this message and exit.", v => showHelp = v != null }, { "stdin", "Read path list from strandard input.", v => readStdIn = v != null }, { "nostderr", "Write errors into stdout instead of stderr.", v => noStdErr = v != null }, { "copy", "Copy the result to clipboard instead of standard output.", v => copyToClipboard = v != null }, { "version", "Print version and exit.", v => showVersion = v != null }, }; try { List <string> localPaths = options.Parse(args); TextWriter output; StringWriter outputSW; if (copyToClipboard) { outputSW = new StringWriter(); output = outputSW; } else { outputSW = null; output = Console.Out; } if (noStdErr) { Console.SetError(output); } if (readStdIn) { string s; while ((s = Console.In.ReadLine()) != null) { localPaths.Add(s); } } if (showVersion) { Console.WriteLine(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString()); exitCode = 0; } else if (showHelp) { exitCode = 0; } else { if (localPaths.Count < 1) { throw new InputError("local path not specified"); } // Make the path searches case insensitive on Windows and // case sensitive on anything else. // This is inexact as on the same machine it's possible to have multiple // file systems with different case sensitivity, but let's keep things // simple. In worst case the link won't be created. bool caseSensitiveFileSystem; switch (Environment.OSVersion.Platform) { case PlatformID.Win32NT: caseSensitiveFileSystem = false; break; case PlatformID.Win32Windows: caseSensitiveFileSystem = false; break; case PlatformID.WinCE: caseSensitiveFileSystem = false; break; default: caseSensitiveFileSystem = true; break; } SharesList shares = new SharesList(caseSensitiveFileSystem); if (sharesfile == null) { switch (Environment.OSVersion.Platform) { case PlatformID.Unix: // Load the samba shares. SambaShareLoader.LoadUserShares(shares); SambaShareLoader.LoadGlobalSambaShares(shares); break; // case PlatformID.MacOSX: case PlatformID.Win32NT: WindowsShareLoader.LoadGlobalShares(shares); break; case PlatformID.Win32Windows: // will it work? WindowsShareLoader.LoadGlobalShares(shares); break; case PlatformID.WinCE: // will it work? WindowsShareLoader.LoadGlobalShares(shares); break; default: throw new Exception(string.Format("Unknown platform {0}. Could not get LAN shares from the system.", Environment.OSVersion.Platform)); } } else { // For the custom share list we use the same format as the "net usershare info" command. string shareListIniContent = File.ReadAllText(sharesfile, Encoding.UTF8); SambaShareLoader.ParseNetUserShareList(shares, shareListIniContent); } if (host == null) { if (hostType == HostType.Ip || hostType == HostType.Ip6) { host = FindIPAddress(hostType == HostType.Ip6); } else if (hostType == HostType.Netbios) { host = System.Environment.MachineName; } else if (hostType == HostType.Hostname) { host = Dns.GetHostName(); } else { throw new InputError(String.Format("unexpected host-type option: {0}", hostType)); } } /* * Small note on complexity. For now the complexity is exponential. * As long as there aren't many shares AND many paths to convert it * shouldn't matter much. Otherwise a better solution is needed with * some data structure. For example searching in a sorted list of * shares using binary search. * * Could implement a binary search on SortedList<string,string> or * use a ready made class prefixdictionary/trie. */ bool first = true; bool foundAll = true; foreach (string localPath in localPaths) { if (first) { first = false; } else { output.WriteLine(); } string link = null; TokenizedLocalPath tokenizedLocalPath = null; try { // Do some sanity checks and make the path absolute. string localAbsolutePath; { // Uri uri = null; // if (Uri.TryCreate(localPath, UriKind.RelativeOrAbsolute, out uri)) { // if (!uri.IsFile) // throw new ArgumentException(string.Format("Could not resolve path {0} to URI. Only file/directory paths are supported.", localPath)); // // // Specific case for URIs like file://host/..., but I haven't seen them in the wild. // if (uri.Host != null && uri.Host != "") // throw new ArgumentException(string.Format("Could not resolve path {0} to URI. Only file/directory paths are supported.", localPath)); // // // replace the path with the absolute one taken from the URI // localAbsolutePath = uri.LocalPath; // } else { // use the passed path as-is localAbsolutePath = localPath; // } } tokenizedLocalPath = new TokenizedLocalPath(localAbsolutePath, Path.DirectorySeparatorChar); } catch (Exception ex) { Console.Error.WriteLine(ex.ToString()); } if (tokenizedLocalPath != null) { link = LinkMaker.MakeLink(linkFormat, host, shares, tokenizedLocalPath, Path.DirectorySeparatorChar); } else { link = localPath; } if (link != null) { output.Write(link); } else { Console.Error.WriteLine(string.Format("No share found containing local path \"{0}\"", localPath)); output.Write(localPath); foundAll = false; } } if (copyToClipboard) { string text = outputSW.ToString(); ClipboardHelper.CopyText(text); } if (foundAll) { // OK. If we reached this point then everything went O~K. exitCode = 0; } } } catch (InputError e) { Console.Error.WriteLine(e.Message); showHelp = true; } catch (OptionException e) { Console.Error.WriteLine(e.ToString()); showHelp = true; } catch (Exception ex) { Console.Error.WriteLine("Unexpected error: " + ex.ToString()); } if (showHelp) { Console.WriteLine("Usage:"); Console.WriteLine("\t" + Path.GetFileNameWithoutExtension(System.Environment.CommandLine) + " <local-path> [<local-path2>, ...]"); Console.WriteLine(); Console.WriteLine("Options:"); options.WriteOptionDescriptions(Console.Out); } return(exitCode); }
public Share(string name, TokenizedLocalPath localPath) { this.name = name; this.nameEscaped = Uri.EscapeUriString(name); this.localPath = localPath; }