michael@0: /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: #include "nsIServiceManager.h" michael@0: michael@0: #include "nsLocalFile.h" // includes platform-specific headers michael@0: michael@0: #include "nsString.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsPrintfCString.h" michael@0: #include "nsCRT.h" michael@0: #include "nsNativeCharsetUtils.h" michael@0: #include "nsUTF8Utils.h" michael@0: michael@0: #ifdef XP_WIN michael@0: #include michael@0: #endif michael@0: michael@0: michael@0: void NS_StartupLocalFile() michael@0: { michael@0: nsLocalFile::GlobalInit(); michael@0: } michael@0: michael@0: void NS_ShutdownLocalFile() michael@0: { michael@0: nsLocalFile::GlobalShutdown(); michael@0: } michael@0: michael@0: #if !defined(MOZ_WIDGET_COCOA) && !defined(XP_WIN) michael@0: NS_IMETHODIMP michael@0: nsLocalFile::InitWithFile(nsIFile *aFile) michael@0: { michael@0: if (NS_WARN_IF(!aFile)) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: nsAutoCString path; michael@0: aFile->GetNativePath(path); michael@0: if (path.IsEmpty()) michael@0: return NS_ERROR_INVALID_ARG; michael@0: return InitWithNativePath(path); michael@0: } michael@0: #endif michael@0: michael@0: #define kMaxFilenameLength 255 michael@0: #define kMaxExtensionLength 100 michael@0: #define kMaxSequenceNumberLength 5 // "-9999" michael@0: // requirement: kMaxExtensionLength < kMaxFilenameLength - kMaxSequenceNumberLength michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::CreateUnique(uint32_t type, uint32_t attributes) michael@0: { michael@0: nsresult rv; michael@0: bool longName; michael@0: michael@0: #ifdef XP_WIN michael@0: nsAutoString pathName, leafName, rootName, suffix; michael@0: rv = GetPath(pathName); michael@0: #else michael@0: nsAutoCString pathName, leafName, rootName, suffix; michael@0: rv = GetNativePath(pathName); michael@0: #endif michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: longName = (pathName.Length() + kMaxSequenceNumberLength > michael@0: kMaxFilenameLength); michael@0: if (!longName) michael@0: { michael@0: rv = Create(type, attributes); michael@0: if (rv != NS_ERROR_FILE_ALREADY_EXISTS) michael@0: return rv; michael@0: } michael@0: michael@0: #ifdef XP_WIN michael@0: rv = GetLeafName(leafName); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: const int32_t lastDot = leafName.RFindChar(char16_t('.')); michael@0: #else michael@0: rv = GetNativeLeafName(leafName); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: const int32_t lastDot = leafName.RFindChar('.'); michael@0: #endif michael@0: michael@0: if (lastDot == kNotFound) michael@0: { michael@0: rootName = leafName; michael@0: } michael@0: else michael@0: { michael@0: suffix = Substring(leafName, lastDot); // include '.' michael@0: rootName = Substring(leafName, 0, lastDot); // strip suffix and dot michael@0: } michael@0: michael@0: if (longName) michael@0: { michael@0: int32_t maxRootLength = (kMaxFilenameLength - michael@0: (pathName.Length() - leafName.Length()) - michael@0: suffix.Length() - kMaxSequenceNumberLength); michael@0: michael@0: // We cannot create an item inside a directory whose name is too long. michael@0: // Also, ensure that at least one character remains after we truncate michael@0: // the root name, as we don't want to end up with an empty leaf name. michael@0: if (maxRootLength < 2) michael@0: return NS_ERROR_FILE_UNRECOGNIZED_PATH; michael@0: michael@0: #ifdef XP_WIN michael@0: // ensure that we don't cut the name in mid-UTF16-character michael@0: rootName.SetLength(NS_IS_LOW_SURROGATE(rootName[maxRootLength]) ? michael@0: maxRootLength - 1 : maxRootLength); michael@0: SetLeafName(rootName + suffix); michael@0: #else michael@0: if (NS_IsNativeUTF8()) michael@0: { michael@0: // ensure that we don't cut the name in mid-UTF8-character michael@0: // (assume the name is valid UTF8 to begin with) michael@0: while (UTF8traits::isInSeq(rootName[maxRootLength])) michael@0: --maxRootLength; michael@0: michael@0: // Another check to avoid ending up with an empty leaf name. michael@0: if (maxRootLength == 0 && suffix.IsEmpty()) michael@0: return NS_ERROR_FILE_UNRECOGNIZED_PATH; michael@0: } michael@0: michael@0: rootName.SetLength(maxRootLength); michael@0: SetNativeLeafName(rootName + suffix); michael@0: #endif michael@0: nsresult rv = Create(type, attributes); michael@0: if (rv != NS_ERROR_FILE_ALREADY_EXISTS) michael@0: return rv; michael@0: } michael@0: michael@0: for (int indx = 1; indx < 10000; indx++) michael@0: { michael@0: // start with "Picture-1.jpg" after "Picture.jpg" exists michael@0: #ifdef XP_WIN michael@0: SetLeafName(rootName + michael@0: NS_ConvertASCIItoUTF16(nsPrintfCString("-%d", indx)) + michael@0: suffix); michael@0: #else michael@0: SetNativeLeafName(rootName + nsPrintfCString("-%d", indx) + suffix); michael@0: #endif michael@0: rv = Create(type, attributes); michael@0: if (NS_SUCCEEDED(rv) || rv != NS_ERROR_FILE_ALREADY_EXISTS) michael@0: return rv; michael@0: } michael@0: michael@0: // The disk is full, sort of michael@0: return NS_ERROR_FILE_TOO_BIG; michael@0: } michael@0: michael@0: #if defined(XP_WIN) michael@0: static const char16_t kPathSeparatorChar = '\\'; michael@0: #elif defined(XP_UNIX) michael@0: static const char16_t kPathSeparatorChar = '/'; michael@0: #else michael@0: #error Need to define file path separator for your platform michael@0: #endif michael@0: michael@0: static int32_t SplitPath(char16_t *path, char16_t **nodeArray, int32_t arrayLen) michael@0: { michael@0: if (*path == 0) michael@0: return 0; michael@0: michael@0: char16_t **nodePtr = nodeArray; michael@0: if (*path == kPathSeparatorChar) michael@0: path++; michael@0: *nodePtr++ = path; michael@0: michael@0: for (char16_t *cp = path; *cp != 0; cp++) { michael@0: if (*cp == kPathSeparatorChar) { michael@0: *cp++ = 0; michael@0: if (*cp == 0) michael@0: break; michael@0: if (nodePtr - nodeArray >= arrayLen) michael@0: return -1; michael@0: *nodePtr++ = cp; michael@0: } michael@0: } michael@0: return nodePtr - nodeArray; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::GetRelativeDescriptor(nsIFile *fromFile, nsACString& _retval) michael@0: { michael@0: if (NS_WARN_IF(!fromFile)) michael@0: return NS_ERROR_INVALID_ARG; michael@0: const int32_t kMaxNodesInPath = 32; michael@0: michael@0: // michael@0: // _retval will be UTF-8 encoded michael@0: // michael@0: michael@0: nsresult rv; michael@0: _retval.Truncate(0); michael@0: michael@0: nsAutoString thisPath, fromPath; michael@0: char16_t *thisNodes[kMaxNodesInPath], *fromNodes[kMaxNodesInPath]; michael@0: int32_t thisNodeCnt, fromNodeCnt, nodeIndex; michael@0: michael@0: rv = GetPath(thisPath); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: rv = fromFile->GetPath(fromPath); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // get raw pointer to mutable string buffer michael@0: char16_t *thisPathPtr; thisPath.BeginWriting(thisPathPtr); michael@0: char16_t *fromPathPtr; fromPath.BeginWriting(fromPathPtr); michael@0: michael@0: thisNodeCnt = SplitPath(thisPathPtr, thisNodes, kMaxNodesInPath); michael@0: fromNodeCnt = SplitPath(fromPathPtr, fromNodes, kMaxNodesInPath); michael@0: if (thisNodeCnt < 0 || fromNodeCnt < 0) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: for (nodeIndex = 0; nodeIndex < thisNodeCnt && nodeIndex < fromNodeCnt; ++nodeIndex) { michael@0: #ifdef XP_WIN michael@0: if (_wcsicmp(char16ptr_t(thisNodes[nodeIndex]), char16ptr_t(fromNodes[nodeIndex]))) michael@0: break; michael@0: #else michael@0: if (nsCRT::strcmp(thisNodes[nodeIndex], fromNodes[nodeIndex])) michael@0: break; michael@0: #endif michael@0: } michael@0: michael@0: int32_t branchIndex = nodeIndex; michael@0: for (nodeIndex = branchIndex; nodeIndex < fromNodeCnt; nodeIndex++) michael@0: _retval.AppendLiteral("../"); michael@0: for (nodeIndex = branchIndex; nodeIndex < thisNodeCnt; nodeIndex++) { michael@0: NS_ConvertUTF16toUTF8 nodeStr(thisNodes[nodeIndex]); michael@0: _retval.Append(nodeStr); michael@0: if (nodeIndex + 1 < thisNodeCnt) michael@0: _retval.Append('/'); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::SetRelativeDescriptor(nsIFile *fromFile, const nsACString& relativeDesc) michael@0: { michael@0: NS_NAMED_LITERAL_CSTRING(kParentDirStr, "../"); michael@0: michael@0: nsCOMPtr targetFile; michael@0: nsresult rv = fromFile->Clone(getter_AddRefs(targetFile)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // michael@0: // relativeDesc is UTF-8 encoded michael@0: // michael@0: michael@0: nsCString::const_iterator strBegin, strEnd; michael@0: relativeDesc.BeginReading(strBegin); michael@0: relativeDesc.EndReading(strEnd); michael@0: michael@0: nsCString::const_iterator nodeBegin(strBegin), nodeEnd(strEnd); michael@0: nsCString::const_iterator pos(strBegin); michael@0: michael@0: nsCOMPtr parentDir; michael@0: while (FindInReadable(kParentDirStr, nodeBegin, nodeEnd)) { michael@0: rv = targetFile->GetParent(getter_AddRefs(parentDir)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: if (!parentDir) michael@0: return NS_ERROR_FILE_UNRECOGNIZED_PATH; michael@0: targetFile = parentDir; michael@0: michael@0: nodeBegin = nodeEnd; michael@0: pos = nodeEnd; michael@0: nodeEnd = strEnd; michael@0: } michael@0: michael@0: nodeBegin = nodeEnd = pos; michael@0: while (nodeEnd != strEnd) { michael@0: FindCharInReadable('/', nodeEnd, strEnd); michael@0: targetFile->Append(NS_ConvertUTF8toUTF16(Substring(nodeBegin, nodeEnd))); michael@0: if (nodeEnd != strEnd) // If there's more left in the string, inc over the '/' nodeEnd is on. michael@0: ++nodeEnd; michael@0: nodeBegin = nodeEnd; michael@0: } michael@0: michael@0: return InitWithFile(targetFile); michael@0: }